mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-22 00:06:12 -05:00
Add AWS S3 authentication and external storage
Added python tests as well
This commit is contained in:
parent
f4d2f27fbd
commit
7a157038d0
@ -123,6 +123,9 @@ else
|
|||||||
COMMAND=bash
|
COMMAND=bash
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Create an empty minio folder with appropriate permissions so www user can write inside it
|
||||||
|
mkdir -p /tmp/minio_tests/test_bucket && chmod -R 777 /tmp/minio_tests
|
||||||
|
|
||||||
# Create an empty webdav folder with appropriate permissions so www user can write inside it
|
# Create an empty webdav folder with appropriate permissions so www user can write inside it
|
||||||
mkdir -p /tmp/webdav_tests && chmod 777 /tmp/webdav_tests
|
mkdir -p /tmp/webdav_tests && chmod 777 /tmp/webdav_tests
|
||||||
|
|
||||||
|
@ -18,6 +18,15 @@ services:
|
|||||||
- ${QGIS_WORKSPACE}/.docker/webdav/passwords.list:/etc/nginx/.passwords.list
|
- ${QGIS_WORKSPACE}/.docker/webdav/passwords.list:/etc/nginx/.passwords.list
|
||||||
- /tmp/webdav_tests:/tmp/webdav_tests_root/webdav_tests
|
- /tmp/webdav_tests:/tmp/webdav_tests_root/webdav_tests
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio
|
||||||
|
volumes:
|
||||||
|
- /tmp/minio_tests:/data
|
||||||
|
environment:
|
||||||
|
- MINIO_ROOT_USER=minioadmin
|
||||||
|
- MINIO_ROOT_PASSWORD=adminio€
|
||||||
|
command: server /data
|
||||||
|
|
||||||
qgis-deps:
|
qgis-deps:
|
||||||
tty: true
|
tty: true
|
||||||
image: qgis3-build-deps-binary-image
|
image: qgis3-build-deps-binary-image
|
||||||
@ -27,6 +36,7 @@ services:
|
|||||||
links:
|
links:
|
||||||
# - mssql
|
# - mssql
|
||||||
- webdav
|
- webdav
|
||||||
|
- minio
|
||||||
- httpbin
|
- httpbin
|
||||||
env_file:
|
env_file:
|
||||||
- docker-variables.env
|
- docker-variables.env
|
||||||
|
@ -208,6 +208,33 @@ EOT
|
|||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Wait for Minio container to be ready
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
if [ $# -eq 0 ] || [ $1 = "ALL_BUT_PROVIDERS" ] || [ $1 = "ALL" ] ; then
|
||||||
|
|
||||||
|
echo "::group::Setup Minio"
|
||||||
|
|
||||||
|
echo "Wait for minio to be ready..."
|
||||||
|
COUNT=0
|
||||||
|
while ! curl http://$QGIS_MINIO_HOST:$QGIS_MINIO_PORT &> /dev/null;
|
||||||
|
do
|
||||||
|
printf "."
|
||||||
|
sleep 5
|
||||||
|
if [[ $(( COUNT++ )) -eq 40 ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ ${COUNT} -eq 41 ]]; then
|
||||||
|
echo "Error: Minio docker timeout!!!"
|
||||||
|
else
|
||||||
|
echo "done"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "::endgroup::"
|
||||||
|
fi
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Wait for WebDAV container to be ready
|
# Wait for WebDAV container to be ready
|
||||||
#######################################
|
#######################################
|
||||||
|
@ -21,5 +21,8 @@ PUSH_TO_CDASH=false
|
|||||||
|
|
||||||
XDG_RUNTIME_DIR=/tmp
|
XDG_RUNTIME_DIR=/tmp
|
||||||
|
|
||||||
|
QGIS_MINIO_HOST=minio
|
||||||
|
QGIS_MINIO_PORT=9000
|
||||||
|
|
||||||
QGIS_WEBDAV_HOST=webdav
|
QGIS_WEBDAV_HOST=webdav
|
||||||
QGIS_WEBDAV_PORT=80
|
QGIS_WEBDAV_PORT=80
|
||||||
|
1
.github/workflows/run-tests.yml
vendored
1
.github/workflows/run-tests.yml
vendored
@ -396,6 +396,7 @@ jobs:
|
|||||||
echo "TEST_BATCH=$TEST_BATCH"
|
echo "TEST_BATCH=$TEST_BATCH"
|
||||||
echo "DOCKERFILE=$DOCKERFILE"
|
echo "DOCKERFILE=$DOCKERFILE"
|
||||||
mkdir -p /tmp/webdav_tests && chmod 777 /tmp/webdav_tests
|
mkdir -p /tmp/webdav_tests && chmod 777 /tmp/webdav_tests
|
||||||
|
mkdir -p /tmp/minio_tests/test_bucket && chmod -R 777 /tmp/minio_tests
|
||||||
docker-compose -f .docker/$DOCKERFILE run qgis-deps /root/QGIS/.docker/docker-qgis-test.sh $TEST_BATCH
|
docker-compose -f .docker/$DOCKERFILE run qgis-deps /root/QGIS/.docker/docker-qgis-test.sh $TEST_BATCH
|
||||||
|
|
||||||
- name: Archive test results report
|
- name: Archive test results report
|
||||||
|
@ -19,6 +19,7 @@ add_subdirectory(pkipaths)
|
|||||||
add_subdirectory(pkipkcs12)
|
add_subdirectory(pkipkcs12)
|
||||||
add_subdirectory(apiheader)
|
add_subdirectory(apiheader)
|
||||||
add_subdirectory(maptiler_hmacsha256)
|
add_subdirectory(maptiler_hmacsha256)
|
||||||
|
add_subdirectory(awss3)
|
||||||
|
|
||||||
if (WITH_OAUTH2_PLUGIN)
|
if (WITH_OAUTH2_PLUGIN)
|
||||||
add_subdirectory(oauth2)
|
add_subdirectory(oauth2)
|
||||||
|
77
src/auth/awss3/CMakeLists.txt
Normal file
77
src/auth/awss3/CMakeLists.txt
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
set(AUTH_AWSS3_SRCS
|
||||||
|
core/qgsauthawss3method.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(AUTH_AWSS3_HDRS
|
||||||
|
core/qgsauthawss3method.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(AUTH_AWSS3_UIS_H "")
|
||||||
|
|
||||||
|
if (WITH_GUI)
|
||||||
|
set(AUTH_AWSS3_SRCS ${AUTH_AWSS3_SRCS}
|
||||||
|
gui/qgsauthawss3edit.cpp
|
||||||
|
)
|
||||||
|
set(AUTH_AWSS3_HDRS ${AUTH_AWSS3_HDRS}
|
||||||
|
gui/qgsauthawss3edit.h
|
||||||
|
)
|
||||||
|
set(AUTH_AWSS3_UIS gui/qgsauthawss3edit.ui)
|
||||||
|
if (BUILD_WITH_QT6)
|
||||||
|
QT6_WRAP_UI(AUTH_AWSS3_UIS_H ${AUTH_AWSS3_UIS})
|
||||||
|
else()
|
||||||
|
QT5_WRAP_UI(AUTH_AWSS3_UIS_H ${AUTH_AWSS3_UIS})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
# static library
|
||||||
|
add_library(authmethod_awss3_a STATIC ${AUTH_AWSS3_SRCS} ${AUTH_AWSS3_HDRS} ${AUTH_AWSS3_UIS_H})
|
||||||
|
|
||||||
|
target_include_directories(authmethod_awss3_a PUBLIC ${CMAKE_SOURCE_DIR}/src/auth/awss3/core)
|
||||||
|
|
||||||
|
# require c++17
|
||||||
|
target_compile_features(authmethod_awss3_a PRIVATE cxx_std_17)
|
||||||
|
|
||||||
|
target_link_libraries(authmethod_awss3_a qgis_core)
|
||||||
|
|
||||||
|
if (WITH_GUI)
|
||||||
|
target_include_directories(authmethod_awss3_a PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src/auth/awss3/gui
|
||||||
|
${CMAKE_BINARY_DIR}/src/auth/awss3
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries (authmethod_awss3_a qgis_gui)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_definitions(authmethod_awss3_a PRIVATE "-DQT_NO_FOREACH")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (FORCE_STATIC_LIBS)
|
||||||
|
# for (external) mobile apps to be able to pick up provider for linking
|
||||||
|
install (TARGETS authmethod_awss3_a ARCHIVE DESTINATION ${QGIS_PLUGIN_DIR})
|
||||||
|
else()
|
||||||
|
# dynamically loaded module
|
||||||
|
add_library(authmethod_awss3 MODULE ${AUTH_AWSS3_SRCS} ${AUTH_AWSS3_HDRS} ${AUTH_AWSS3_UIS_H})
|
||||||
|
|
||||||
|
# require c++17
|
||||||
|
target_compile_features(authmethod_awss3 PRIVATE cxx_std_17)
|
||||||
|
|
||||||
|
target_link_libraries(authmethod_awss3 qgis_core)
|
||||||
|
|
||||||
|
if (WITH_GUI)
|
||||||
|
target_include_directories(authmethod_awss3 PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src/auth/awss3/gui
|
||||||
|
${CMAKE_BINARY_DIR}/src/auth/awss3
|
||||||
|
)
|
||||||
|
target_link_libraries (authmethod_awss3 qgis_gui)
|
||||||
|
add_dependencies(authmethod_awss3 ui)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_definitions(authmethod_awss3 PRIVATE "-DQT_NO_FOREACH")
|
||||||
|
|
||||||
|
install (TARGETS authmethod_awss3
|
||||||
|
RUNTIME DESTINATION ${QGIS_PLUGIN_DIR}
|
||||||
|
LIBRARY DESTINATION ${QGIS_PLUGIN_DIR}
|
||||||
|
)
|
||||||
|
endif()
|
208
src/auth/awss3/core/qgsauthawss3method.cpp
Normal file
208
src/auth/awss3/core/qgsauthawss3method.cpp
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgsauthawss3method.cpp
|
||||||
|
--------------------------------------
|
||||||
|
Date : December 2022
|
||||||
|
Copyright : (C) 2022 by Jacky Volpes
|
||||||
|
Email : jacky dot volpes at oslandia 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 "qgsauthawss3method.h"
|
||||||
|
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QMessageAuthenticationCode>
|
||||||
|
|
||||||
|
#include "qgsauthmanager.h"
|
||||||
|
#include "qgslogger.h"
|
||||||
|
#include "qgsapplication.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_GUI
|
||||||
|
#include "qgsauthawss3edit.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
const QString QgsAuthAwsS3Method::AUTH_METHOD_KEY = QStringLiteral( "AWSS3" );
|
||||||
|
const QString QgsAuthAwsS3Method::AUTH_METHOD_DESCRIPTION = QStringLiteral( "AWS S3" );
|
||||||
|
const QString QgsAuthAwsS3Method::AUTH_METHOD_DISPLAY_DESCRIPTION = tr( "AWS S3" );
|
||||||
|
|
||||||
|
QMap<QString, QgsAuthMethodConfig> QgsAuthAwsS3Method::sAuthConfigCache = QMap<QString, QgsAuthMethodConfig>();
|
||||||
|
|
||||||
|
|
||||||
|
QgsAuthAwsS3Method::QgsAuthAwsS3Method()
|
||||||
|
{
|
||||||
|
setVersion( 4 );
|
||||||
|
setExpansions( QgsAuthMethod::NetworkRequest );
|
||||||
|
setDataProviders( QStringList() << QStringLiteral( "awss3" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QgsAuthAwsS3Method::key() const
|
||||||
|
{
|
||||||
|
return AUTH_METHOD_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QgsAuthAwsS3Method::description() const
|
||||||
|
{
|
||||||
|
return AUTH_METHOD_DESCRIPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QgsAuthAwsS3Method::displayDescription() const
|
||||||
|
{
|
||||||
|
return AUTH_METHOD_DISPLAY_DESCRIPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsAuthAwsS3Method::updateNetworkRequest( QNetworkRequest &request, const QString &authcfg,
|
||||||
|
const QString &dataprovider )
|
||||||
|
{
|
||||||
|
Q_UNUSED( dataprovider )
|
||||||
|
const QgsAuthMethodConfig config = getMethodConfig( authcfg );
|
||||||
|
if ( !config.isValid() )
|
||||||
|
{
|
||||||
|
QgsDebugMsg( QStringLiteral( "Update request config FAILED for authcfg: %1: config invalid" ).arg( authcfg ) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray username = config.config( QStringLiteral( "username" ) ).toLocal8Bit();
|
||||||
|
const QByteArray password = config.config( QStringLiteral( "password" ) ).toLocal8Bit();
|
||||||
|
const QByteArray region = config.config( QStringLiteral( "region" ) ).toLocal8Bit();
|
||||||
|
|
||||||
|
const QByteArray headerList = "host;x-amz-content-sha256;x-amz-date";
|
||||||
|
const QByteArray encryptionMethod = "AWS4-HMAC-SHA256";
|
||||||
|
const QDateTime currentDateTime = QDateTime::currentDateTime().toUTC();
|
||||||
|
const QByteArray date = currentDateTime.toString( "yyyyMMdd" ).toLocal8Bit();
|
||||||
|
const QByteArray dateTime = currentDateTime.toString( "yyyyMMddThhmmssZ" ).toLocal8Bit();
|
||||||
|
|
||||||
|
QByteArray canonicalPath = QUrl::toPercentEncoding( request.url().path(), "/" ); // Don't encode slash
|
||||||
|
if ( canonicalPath.isEmpty() )
|
||||||
|
{
|
||||||
|
canonicalPath = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray method;
|
||||||
|
QByteArray payloadHash;
|
||||||
|
if ( request.hasRawHeader( "X-Amz-Content-SHA256" ) )
|
||||||
|
{
|
||||||
|
method = "PUT";
|
||||||
|
payloadHash = request.rawHeader( "X-Amz-Content-SHA256" );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
method = "GET";
|
||||||
|
payloadHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; // Sha256 of empty payload
|
||||||
|
request.setRawHeader( QByteArray( "X-Amz-Content-SHA256" ), payloadHash );
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray canonicalRequest = method + '\n' +
|
||||||
|
canonicalPath + '\n' +
|
||||||
|
'\n' +
|
||||||
|
"host:" + request.url().host().toLocal8Bit() + '\n' +
|
||||||
|
"x-amz-content-sha256:" + payloadHash + '\n' +
|
||||||
|
"x-amz-date:" + dateTime + '\n' +
|
||||||
|
'\n' +
|
||||||
|
headerList + '\n' +
|
||||||
|
payloadHash;
|
||||||
|
|
||||||
|
const QByteArray canonicalRequestHash = QCryptographicHash::hash( canonicalRequest, QCryptographicHash::Sha256 ).toHex();
|
||||||
|
const QByteArray stringToSign = encryptionMethod + '\n' +
|
||||||
|
dateTime + '\n' +
|
||||||
|
date + "/" + region + "/s3/aws4_request" + '\n' +
|
||||||
|
canonicalRequestHash;
|
||||||
|
|
||||||
|
const QByteArray signingKey = QMessageAuthenticationCode::hash( "aws4_request",
|
||||||
|
QMessageAuthenticationCode::hash( "s3",
|
||||||
|
QMessageAuthenticationCode::hash( region,
|
||||||
|
QMessageAuthenticationCode::hash( date, "AWS4" + password,
|
||||||
|
QCryptographicHash::Sha256 ),
|
||||||
|
QCryptographicHash::Sha256 ),
|
||||||
|
QCryptographicHash::Sha256 ),
|
||||||
|
QCryptographicHash::Sha256 );
|
||||||
|
|
||||||
|
const QByteArray signature = QMessageAuthenticationCode::hash( stringToSign, signingKey, QCryptographicHash::Sha256 ).toHex();
|
||||||
|
|
||||||
|
request.setRawHeader( QByteArray( "Host" ), request.url().host().toLocal8Bit() );
|
||||||
|
request.setRawHeader( QByteArray( "X-Amz-Date" ), dateTime );
|
||||||
|
request.setRawHeader( QByteArray( "Authorization" ),
|
||||||
|
encryptionMethod + "Credential=" + username + '/' + date + "/" + region + "/s3/aws4_request, SignedHeaders=" + headerList + ", Signature=" + signature );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Method::clearCachedConfig( const QString &authcfg )
|
||||||
|
{
|
||||||
|
removeMethodConfig( authcfg );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Method::updateMethodConfig( QgsAuthMethodConfig &mconfig )
|
||||||
|
{
|
||||||
|
Q_UNUSED( mconfig );
|
||||||
|
// NOTE: add updates as method version() increases due to config storage changes
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsAuthMethodConfig QgsAuthAwsS3Method::getMethodConfig( const QString &authcfg, bool fullconfig )
|
||||||
|
{
|
||||||
|
const QMutexLocker locker( &mMutex );
|
||||||
|
QgsAuthMethodConfig config;
|
||||||
|
|
||||||
|
// check if it is cached
|
||||||
|
if ( sAuthConfigCache.contains( authcfg ) )
|
||||||
|
{
|
||||||
|
config = sAuthConfigCache.value( authcfg );
|
||||||
|
QgsDebugMsgLevel( QStringLiteral( "Retrieved config for authcfg: %1" ).arg( authcfg ), 2 );
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else build bundle
|
||||||
|
if ( !QgsApplication::authManager()->loadAuthenticationConfig( authcfg, config, fullconfig ) )
|
||||||
|
{
|
||||||
|
QgsDebugMsgLevel( QStringLiteral( "Retrieved config for authcfg: %1" ).arg( authcfg ), 2 );
|
||||||
|
return QgsAuthMethodConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache bundle
|
||||||
|
putMethodConfig( authcfg, config );
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Method::putMethodConfig( const QString &authcfg, const QgsAuthMethodConfig &mconfig )
|
||||||
|
{
|
||||||
|
const QMutexLocker locker( &mMutex );
|
||||||
|
QgsDebugMsgLevel( QStringLiteral( "Putting AWS S3 config for authcfg: %1" ).arg( authcfg ), 2 );
|
||||||
|
sAuthConfigCache.insert( authcfg, mconfig );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Method::removeMethodConfig( const QString &authcfg )
|
||||||
|
{
|
||||||
|
const QMutexLocker locker( &mMutex );
|
||||||
|
if ( sAuthConfigCache.contains( authcfg ) )
|
||||||
|
{
|
||||||
|
sAuthConfigCache.remove( authcfg );
|
||||||
|
QgsDebugMsgLevel( QStringLiteral( "Removed Aws S3 config for authcfg: %1" ).arg( authcfg ), 2 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_GUI
|
||||||
|
QWidget *QgsAuthAwsS3Method::editWidget( QWidget *parent ) const
|
||||||
|
{
|
||||||
|
return new QgsAuthAwsS3Edit( parent );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// Plugin externals
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef HAVE_STATIC_PROVIDERS
|
||||||
|
QGISEXTERN QgsAuthMethodMetadata *authMethodMetadataFactory()
|
||||||
|
{
|
||||||
|
return new QgsAuthAwsS3MethodMetadata();
|
||||||
|
}
|
||||||
|
#endif
|
77
src/auth/awss3/core/qgsauthawss3method.h
Normal file
77
src/auth/awss3/core/qgsauthawss3method.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgsauthawss3method.h
|
||||||
|
--------------------------------------
|
||||||
|
Date : December 2022
|
||||||
|
Copyright : (C) 2022 by Jacky Volpes
|
||||||
|
Email : jacky dot volpes at oslandia 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 QGSAUTHAWSS3METHOD_H
|
||||||
|
#define QGSAUTHAWSS3METHOD_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
|
#include "qgsauthconfig.h"
|
||||||
|
#include "qgsauthmethod.h"
|
||||||
|
#include "qgsauthmethodmetadata.h"
|
||||||
|
|
||||||
|
|
||||||
|
class QgsAuthAwsS3Method : public QgsAuthMethod
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
static const QString AUTH_METHOD_KEY;
|
||||||
|
static const QString AUTH_METHOD_DESCRIPTION;
|
||||||
|
static const QString AUTH_METHOD_DISPLAY_DESCRIPTION;
|
||||||
|
|
||||||
|
explicit QgsAuthAwsS3Method();
|
||||||
|
|
||||||
|
// QgsAuthMethod interface
|
||||||
|
QString key() const override;
|
||||||
|
|
||||||
|
QString description() const override;
|
||||||
|
|
||||||
|
QString displayDescription() const override;
|
||||||
|
|
||||||
|
bool updateNetworkRequest( QNetworkRequest &request, const QString &authcfg,
|
||||||
|
const QString &dataprovider = QString() ) override;
|
||||||
|
|
||||||
|
void clearCachedConfig( const QString &authcfg ) override;
|
||||||
|
void updateMethodConfig( QgsAuthMethodConfig &mconfig ) override;
|
||||||
|
|
||||||
|
#ifdef HAVE_GUI
|
||||||
|
QWidget *editWidget( QWidget *parent )const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
QgsAuthMethodConfig getMethodConfig( const QString &authcfg, bool fullconfig = true );
|
||||||
|
|
||||||
|
void putMethodConfig( const QString &authcfg, const QgsAuthMethodConfig &mconfig );
|
||||||
|
|
||||||
|
void removeMethodConfig( const QString &authcfg );
|
||||||
|
|
||||||
|
static QMap<QString, QgsAuthMethodConfig> sAuthConfigCache;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class QgsAuthAwsS3MethodMetadata : public QgsAuthMethodMetadata
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QgsAuthAwsS3MethodMetadata()
|
||||||
|
: QgsAuthMethodMetadata( QgsAuthAwsS3Method::AUTH_METHOD_KEY, QgsAuthAwsS3Method::AUTH_METHOD_DESCRIPTION )
|
||||||
|
{}
|
||||||
|
QgsAuthAwsS3Method *createAuthMethod() const override {return new QgsAuthAwsS3Method;}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QGSAUTHAWSS3METHOD_H
|
97
src/auth/awss3/gui/qgsauthawss3edit.cpp
Normal file
97
src/auth/awss3/gui/qgsauthawss3edit.cpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgsauthawss3edit.cpp
|
||||||
|
--------------------------------------
|
||||||
|
Date : December 2022
|
||||||
|
Copyright : (C) 2022 by Jacky Volpes
|
||||||
|
Email : jacky dot volpes at oslandia 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 "qgsauthawss3edit.h"
|
||||||
|
#include "ui_qgsauthawss3edit.h"
|
||||||
|
|
||||||
|
|
||||||
|
QgsAuthAwsS3Edit::QgsAuthAwsS3Edit( QWidget *parent )
|
||||||
|
: QgsAuthMethodEdit( parent )
|
||||||
|
{
|
||||||
|
setupUi( this );
|
||||||
|
connect( leUsername, &QLineEdit::textChanged, this, &QgsAuthAwsS3Edit::leUsername_textChanged );
|
||||||
|
connect( lePassword, &QLineEdit::textChanged, this, &QgsAuthAwsS3Edit::lePassword_textChanged );
|
||||||
|
connect( leRegion, &QLineEdit::textChanged, this, &QgsAuthAwsS3Edit::leRegion_textChanged );
|
||||||
|
connect( chkPasswordShow, &QCheckBox::stateChanged, this, &QgsAuthAwsS3Edit::chkPasswordShow_stateChanged );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsAuthAwsS3Edit::validateConfig()
|
||||||
|
{
|
||||||
|
const bool curvalid = !leUsername->text().isEmpty() && !lePassword->text().isEmpty() && !leRegion->text().isEmpty();
|
||||||
|
if ( mValid != curvalid )
|
||||||
|
{
|
||||||
|
mValid = curvalid;
|
||||||
|
emit validityChanged( curvalid );
|
||||||
|
}
|
||||||
|
return curvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsStringMap QgsAuthAwsS3Edit::configMap() const
|
||||||
|
{
|
||||||
|
QgsStringMap config;
|
||||||
|
config.insert( QStringLiteral( "username" ), leUsername->text() );
|
||||||
|
config.insert( QStringLiteral( "password" ), lePassword->text() );
|
||||||
|
config.insert( QStringLiteral( "region" ), leRegion->text() );
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Edit::loadConfig( const QgsStringMap &configmap )
|
||||||
|
{
|
||||||
|
clearConfig();
|
||||||
|
|
||||||
|
mConfigMap = configmap;
|
||||||
|
leUsername->setText( configmap.value( QStringLiteral( "username" ) ) );
|
||||||
|
lePassword->setText( configmap.value( QStringLiteral( "password" ) ) );
|
||||||
|
leRegion->setText( configmap.value( QStringLiteral( "region" ) ) );
|
||||||
|
|
||||||
|
validateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Edit::resetConfig()
|
||||||
|
{
|
||||||
|
loadConfig( mConfigMap );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Edit::clearConfig()
|
||||||
|
{
|
||||||
|
leUsername->clear();
|
||||||
|
lePassword->clear();
|
||||||
|
leRegion->clear();
|
||||||
|
chkPasswordShow->setChecked( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Edit::leUsername_textChanged( const QString &txt )
|
||||||
|
{
|
||||||
|
Q_UNUSED( txt )
|
||||||
|
validateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Edit::lePassword_textChanged( const QString &txt )
|
||||||
|
{
|
||||||
|
Q_UNUSED( txt )
|
||||||
|
validateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Edit::leRegion_textChanged( const QString &txt )
|
||||||
|
{
|
||||||
|
Q_UNUSED( txt )
|
||||||
|
validateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsAuthAwsS3Edit::chkPasswordShow_stateChanged( int state )
|
||||||
|
{
|
||||||
|
lePassword->setEchoMode( ( state > 0 ) ? QLineEdit::Normal : QLineEdit::Password );
|
||||||
|
}
|
59
src/auth/awss3/gui/qgsauthawss3edit.h
Normal file
59
src/auth/awss3/gui/qgsauthawss3edit.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgsauthawss3edit.h
|
||||||
|
--------------------------------------
|
||||||
|
Date : December 2022
|
||||||
|
Copyright : (C) 2022 by Jacky Volpes
|
||||||
|
Email : jacky dot volpes at oslandia 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 QGSAUTHAWSS3EDIT_H
|
||||||
|
#define QGSAUTHAWSS3EDIT_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "qgsauthmethodedit.h"
|
||||||
|
#include "ui_qgsauthawss3edit.h"
|
||||||
|
|
||||||
|
#include "qgsauthconfig.h"
|
||||||
|
|
||||||
|
|
||||||
|
class QgsAuthAwsS3Edit : public QgsAuthMethodEdit, private Ui::QgsAuthAwsS3Edit
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QgsAuthAwsS3Edit( QWidget *parent = nullptr );
|
||||||
|
|
||||||
|
bool validateConfig() override;
|
||||||
|
|
||||||
|
QgsStringMap configMap() const override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void loadConfig( const QgsStringMap &configmap ) override;
|
||||||
|
|
||||||
|
void resetConfig() override;
|
||||||
|
|
||||||
|
void clearConfig() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void leUsername_textChanged( const QString &txt );
|
||||||
|
|
||||||
|
void lePassword_textChanged( const QString &txt );
|
||||||
|
|
||||||
|
void leRegion_textChanged( const QString &txt );
|
||||||
|
|
||||||
|
void chkPasswordShow_stateChanged( int state );
|
||||||
|
|
||||||
|
private:
|
||||||
|
QgsStringMap mConfigMap;
|
||||||
|
bool mValid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QGSAUTHAWSS3EDIT_H
|
129
src/auth/awss3/gui/qgsauthawss3edit.ui
Normal file
129
src/auth/awss3/gui/qgsauthawss3edit.ui
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>QgsAuthAwsS3Edit</class>
|
||||||
|
<widget class="QWidget" name="QgsAuthAwsS3Edit">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="leRegion">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Required</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="leUsername">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>AWS AccessKeyId</string>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Required</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="lblRegion">
|
||||||
|
<property name="text">
|
||||||
|
<string>Region</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>173</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lePassword">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>AWS SecretAccessKey</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="echoMode">
|
||||||
|
<enum>QLineEdit::Password</enum>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Required</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="chkPasswordShow">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Show</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="lblUsername">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>AWS AccessKeyId</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Username</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="lblPassword">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>AWS SecretAccessKey</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Password</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>leUsername</tabstop>
|
||||||
|
<tabstop>lePassword</tabstop>
|
||||||
|
<tabstop>leRegion</tabstop>
|
||||||
|
<tabstop>chkPasswordShow</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -154,7 +154,7 @@ set(QGIS_CORE_SRCS
|
|||||||
externalstorage/qgsexternalstorage.cpp
|
externalstorage/qgsexternalstorage.cpp
|
||||||
externalstorage/qgsexternalstorageregistry.cpp
|
externalstorage/qgsexternalstorageregistry.cpp
|
||||||
externalstorage/qgssimplecopyexternalstorage.cpp
|
externalstorage/qgssimplecopyexternalstorage.cpp
|
||||||
externalstorage/qgswebdavexternalstorage.cpp
|
externalstorage/qgshttpexternalstorage.cpp
|
||||||
|
|
||||||
layertree/qgscolorramplegendnode.cpp
|
layertree/qgscolorramplegendnode.cpp
|
||||||
layertree/qgscolorramplegendnodesettings.cpp
|
layertree/qgscolorramplegendnodesettings.cpp
|
||||||
@ -1925,7 +1925,7 @@ set(QGIS_CORE_PRIVATE_HDRS
|
|||||||
expression/qgsexpression_p.h
|
expression/qgsexpression_p.h
|
||||||
|
|
||||||
externalstorage/qgssimplecopyexternalstorage_p.h
|
externalstorage/qgssimplecopyexternalstorage_p.h
|
||||||
externalstorage/qgswebdavexternalstorage_p.h
|
externalstorage/qgshttpexternalstorage_p.h
|
||||||
|
|
||||||
proj/qgscoordinatereferencesystem_p.h
|
proj/qgscoordinatereferencesystem_p.h
|
||||||
proj/qgscoordinatetransformcontext_p.h
|
proj/qgscoordinatetransformcontext_p.h
|
||||||
|
@ -17,12 +17,13 @@
|
|||||||
|
|
||||||
#include "qgsexternalstorage.h"
|
#include "qgsexternalstorage.h"
|
||||||
#include "qgssimplecopyexternalstorage_p.h"
|
#include "qgssimplecopyexternalstorage_p.h"
|
||||||
#include "qgswebdavexternalstorage_p.h"
|
#include "qgshttpexternalstorage_p.h"
|
||||||
|
|
||||||
QgsExternalStorageRegistry::QgsExternalStorageRegistry()
|
QgsExternalStorageRegistry::QgsExternalStorageRegistry()
|
||||||
{
|
{
|
||||||
registerExternalStorage( new QgsSimpleCopyExternalStorage() );
|
registerExternalStorage( new QgsSimpleCopyExternalStorage() );
|
||||||
registerExternalStorage( new QgsWebDAVExternalStorage() );
|
registerExternalStorage( new QgsWebDavExternalStorage() );
|
||||||
|
registerExternalStorage( new QgsAwsS3ExternalStorage() );
|
||||||
}
|
}
|
||||||
|
|
||||||
QgsExternalStorageRegistry::~QgsExternalStorageRegistry()
|
QgsExternalStorageRegistry::~QgsExternalStorageRegistry()
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* *
|
* *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include "qgswebdavexternalstorage_p.h"
|
#include "qgshttpexternalstorage_p.h"
|
||||||
|
|
||||||
#include "qgsnetworkcontentfetcherregistry.h"
|
#include "qgsnetworkcontentfetcherregistry.h"
|
||||||
#include "qgsblockingnetworkrequest.h"
|
#include "qgsblockingnetworkrequest.h"
|
||||||
@ -24,10 +24,11 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
|
||||||
///@cond PRIVATE
|
///@cond PRIVATE
|
||||||
|
|
||||||
QgsWebDAVExternalStorageStoreTask::QgsWebDAVExternalStorageStoreTask( const QUrl &url, const QString &filePath, const QString &authCfg )
|
QgsHttpExternalStorageStoreTask::QgsHttpExternalStorageStoreTask( const QUrl &url, const QString &filePath, const QString &authCfg )
|
||||||
: QgsTask( tr( "Storing %1" ).arg( QFileInfo( filePath ).baseName() ) )
|
: QgsTask( tr( "Storing %1" ).arg( QFileInfo( filePath ).baseName() ) )
|
||||||
, mUrl( url )
|
, mUrl( url )
|
||||||
, mFilePath( filePath )
|
, mFilePath( filePath )
|
||||||
@ -36,17 +37,20 @@ QgsWebDAVExternalStorageStoreTask::QgsWebDAVExternalStorageStoreTask( const QUrl
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QgsWebDAVExternalStorageStoreTask::run()
|
bool QgsHttpExternalStorageStoreTask::run()
|
||||||
{
|
{
|
||||||
QgsBlockingNetworkRequest request;
|
QgsBlockingNetworkRequest request;
|
||||||
request.setAuthCfg( mAuthCfg );
|
request.setAuthCfg( mAuthCfg );
|
||||||
|
|
||||||
QNetworkRequest req( mUrl );
|
QNetworkRequest req( mUrl );
|
||||||
QgsSetRequestInitiatorClass( req, QStringLiteral( "QgsWebDAVExternalStorageStoreTask" ) );
|
QgsSetRequestInitiatorClass( req, QStringLiteral( "QgsHttpExternalStorageStoreTask" ) );
|
||||||
|
|
||||||
QFile *f = new QFile( mFilePath );
|
QFile *f = new QFile( mFilePath );
|
||||||
f->open( QIODevice::ReadOnly );
|
f->open( QIODevice::ReadOnly );
|
||||||
|
|
||||||
|
if ( mPrepareRequestHandler )
|
||||||
|
mPrepareRequestHandler( req, f );
|
||||||
|
|
||||||
connect( &request, &QgsBlockingNetworkRequest::uploadProgress, this, [ = ]( qint64 bytesReceived, qint64 bytesTotal )
|
connect( &request, &QgsBlockingNetworkRequest::uploadProgress, this, [ = ]( qint64 bytesReceived, qint64 bytesTotal )
|
||||||
{
|
{
|
||||||
if ( !isCanceled() && bytesTotal > 0 )
|
if ( !isCanceled() && bytesTotal > 0 )
|
||||||
@ -66,24 +70,29 @@ bool QgsWebDAVExternalStorageStoreTask::run()
|
|||||||
return !isCanceled() && err == QgsBlockingNetworkRequest::NoError;
|
return !isCanceled() && err == QgsBlockingNetworkRequest::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsWebDAVExternalStorageStoreTask::cancel()
|
void QgsHttpExternalStorageStoreTask::cancel()
|
||||||
{
|
{
|
||||||
mFeedback->cancel();
|
mFeedback->cancel();
|
||||||
QgsTask::cancel();
|
QgsTask::cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QgsWebDAVExternalStorageStoreTask::errorString() const
|
QString QgsHttpExternalStorageStoreTask::errorString() const
|
||||||
{
|
{
|
||||||
return mErrorString;
|
return mErrorString;
|
||||||
}
|
}
|
||||||
|
|
||||||
QgsWebDAVExternalStorageStoredContent::QgsWebDAVExternalStorageStoredContent( const QString &filePath, const QString &url, const QString &authcfg )
|
void QgsHttpExternalStorageStoreTask::setPrepareRequestHandler( std::function< void( QNetworkRequest &request, QFile *f ) > handler )
|
||||||
|
{
|
||||||
|
mPrepareRequestHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsHttpExternalStorageStoredContent::QgsHttpExternalStorageStoredContent( const QString &filePath, const QString &url, const QString &authcfg )
|
||||||
{
|
{
|
||||||
QString storageUrl = url;
|
QString storageUrl = url;
|
||||||
if ( storageUrl.endsWith( "/" ) )
|
if ( storageUrl.endsWith( "/" ) )
|
||||||
storageUrl.append( QFileInfo( filePath ).fileName() );
|
storageUrl.append( QFileInfo( filePath ).fileName() );
|
||||||
|
|
||||||
mUploadTask = new QgsWebDAVExternalStorageStoreTask( storageUrl, filePath, authcfg );
|
mUploadTask = new QgsHttpExternalStorageStoreTask( storageUrl, filePath, authcfg );
|
||||||
|
|
||||||
connect( mUploadTask, &QgsTask::taskCompleted, this, [ = ]
|
connect( mUploadTask, &QgsTask::taskCompleted, this, [ = ]
|
||||||
{
|
{
|
||||||
@ -103,14 +112,14 @@ QgsWebDAVExternalStorageStoredContent::QgsWebDAVExternalStorageStoredContent( co
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsWebDAVExternalStorageStoredContent::store()
|
void QgsHttpExternalStorageStoredContent::store()
|
||||||
{
|
{
|
||||||
mStatus = Qgis::ContentStatus::Running;
|
mStatus = Qgis::ContentStatus::Running;
|
||||||
QgsApplication::taskManager()->addTask( mUploadTask );
|
QgsApplication::taskManager()->addTask( mUploadTask );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void QgsWebDAVExternalStorageStoredContent::cancel()
|
void QgsHttpExternalStorageStoredContent::cancel()
|
||||||
{
|
{
|
||||||
if ( !mUploadTask )
|
if ( !mUploadTask )
|
||||||
return;
|
return;
|
||||||
@ -125,16 +134,21 @@ void QgsWebDAVExternalStorageStoredContent::cancel()
|
|||||||
mUploadTask->cancel();
|
mUploadTask->cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QgsWebDAVExternalStorageStoredContent::url() const
|
QString QgsHttpExternalStorageStoredContent::url() const
|
||||||
{
|
{
|
||||||
return mUrl;
|
return mUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsHttpExternalStorageStoredContent::setPrepareRequestHandler( std::function< void( QNetworkRequest &request, QFile *f ) > handler )
|
||||||
|
{
|
||||||
|
mUploadTask->setPrepareRequestHandler( handler );
|
||||||
|
}
|
||||||
|
|
||||||
QgsWebDAVExternalStorageFetchedContent::QgsWebDAVExternalStorageFetchedContent( QgsFetchedContent *fetchedContent )
|
|
||||||
|
QgsHttpExternalStorageFetchedContent::QgsHttpExternalStorageFetchedContent( QgsFetchedContent *fetchedContent )
|
||||||
: mFetchedContent( fetchedContent )
|
: mFetchedContent( fetchedContent )
|
||||||
{
|
{
|
||||||
connect( mFetchedContent, &QgsFetchedContent::fetched, this, &QgsWebDAVExternalStorageFetchedContent::onFetched );
|
connect( mFetchedContent, &QgsFetchedContent::fetched, this, &QgsHttpExternalStorageFetchedContent::onFetched );
|
||||||
connect( mFetchedContent, &QgsFetchedContent::errorOccurred, this, [ = ]( QNetworkReply::NetworkError code, const QString & errorMsg )
|
connect( mFetchedContent, &QgsFetchedContent::errorOccurred, this, [ = ]( QNetworkReply::NetworkError code, const QString & errorMsg )
|
||||||
{
|
{
|
||||||
Q_UNUSED( code );
|
Q_UNUSED( code );
|
||||||
@ -142,7 +156,7 @@ QgsWebDAVExternalStorageFetchedContent::QgsWebDAVExternalStorageFetchedContent(
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsWebDAVExternalStorageFetchedContent::fetch()
|
void QgsHttpExternalStorageFetchedContent::fetch()
|
||||||
{
|
{
|
||||||
if ( !mFetchedContent )
|
if ( !mFetchedContent )
|
||||||
return;
|
return;
|
||||||
@ -158,12 +172,12 @@ void QgsWebDAVExternalStorageFetchedContent::fetch()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QgsWebDAVExternalStorageFetchedContent::filePath() const
|
QString QgsHttpExternalStorageFetchedContent::filePath() const
|
||||||
{
|
{
|
||||||
return mFetchedContent ? mFetchedContent->filePath() : QString();
|
return mFetchedContent ? mFetchedContent->filePath() : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsWebDAVExternalStorageFetchedContent::onFetched()
|
void QgsHttpExternalStorageFetchedContent::onFetched()
|
||||||
{
|
{
|
||||||
if ( !mFetchedContent )
|
if ( !mFetchedContent )
|
||||||
return;
|
return;
|
||||||
@ -175,31 +189,68 @@ void QgsWebDAVExternalStorageFetchedContent::onFetched()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsWebDAVExternalStorageFetchedContent::cancel()
|
void QgsHttpExternalStorageFetchedContent::cancel()
|
||||||
{
|
{
|
||||||
mFetchedContent->cancel();
|
mFetchedContent->cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QgsWebDAVExternalStorage::type() const
|
|
||||||
|
// WEB DAV PROTOCOL
|
||||||
|
|
||||||
|
QString QgsWebDavExternalStorage::type() const
|
||||||
{
|
{
|
||||||
return QStringLiteral( "WebDAV" );
|
return QStringLiteral( "WebDAV" );
|
||||||
};
|
};
|
||||||
|
|
||||||
QString QgsWebDAVExternalStorage::displayName() const
|
QString QgsWebDavExternalStorage::displayName() const
|
||||||
{
|
{
|
||||||
return QObject::tr( "WebDAV Storage" );
|
return QObject::tr( "WebDAV Storage" );
|
||||||
};
|
};
|
||||||
|
|
||||||
QgsExternalStorageStoredContent *QgsWebDAVExternalStorage::doStore( const QString &filePath, const QString &url, const QString &authcfg ) const
|
QgsExternalStorageStoredContent *QgsWebDavExternalStorage::doStore( const QString &filePath, const QString &url, const QString &authcfg ) const
|
||||||
{
|
{
|
||||||
return new QgsWebDAVExternalStorageStoredContent( filePath, url, authcfg );
|
return new QgsHttpExternalStorageStoredContent( filePath, url, authcfg );
|
||||||
};
|
};
|
||||||
|
|
||||||
QgsExternalStorageFetchedContent *QgsWebDAVExternalStorage::doFetch( const QString &url, const QString &authConfig ) const
|
QgsExternalStorageFetchedContent *QgsWebDavExternalStorage::doFetch( const QString &url, const QString &authConfig ) const
|
||||||
{
|
{
|
||||||
QgsFetchedContent *fetchedContent = QgsApplication::networkContentFetcherRegistry()->fetch( url, Qgis::ActionStart::Deferred, authConfig );
|
QgsFetchedContent *fetchedContent = QgsApplication::networkContentFetcherRegistry()->fetch( url, Qgis::ActionStart::Deferred, authConfig );
|
||||||
|
|
||||||
return new QgsWebDAVExternalStorageFetchedContent( fetchedContent );
|
return new QgsHttpExternalStorageFetchedContent( fetchedContent );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// AWS S3 PROTOCOL
|
||||||
|
|
||||||
|
QString QgsAwsS3ExternalStorage::type() const
|
||||||
|
{
|
||||||
|
return QStringLiteral( "AWSS3" );
|
||||||
|
};
|
||||||
|
|
||||||
|
QString QgsAwsS3ExternalStorage::displayName() const
|
||||||
|
{
|
||||||
|
return QObject::tr( "AWS S3" );
|
||||||
|
};
|
||||||
|
|
||||||
|
QgsExternalStorageStoredContent *QgsAwsS3ExternalStorage::doStore( const QString &filePath, const QString &url, const QString &authcfg ) const
|
||||||
|
{
|
||||||
|
std::unique_ptr<QgsHttpExternalStorageStoredContent> storedContent = std::make_unique<QgsHttpExternalStorageStoredContent>( filePath, url, authcfg );
|
||||||
|
storedContent->setPrepareRequestHandler( []( QNetworkRequest & request, QFile * f )
|
||||||
|
{
|
||||||
|
QCryptographicHash payloadCrypto( QCryptographicHash::Sha256 );
|
||||||
|
payloadCrypto.addData( f );
|
||||||
|
QByteArray payloadHash = payloadCrypto.result().toHex();
|
||||||
|
f->seek( 0 );
|
||||||
|
request.setRawHeader( QByteArray( "X-Amz-Content-SHA256" ), payloadHash );
|
||||||
|
} );
|
||||||
|
|
||||||
|
return storedContent.release();
|
||||||
|
};
|
||||||
|
|
||||||
|
QgsExternalStorageFetchedContent *QgsAwsS3ExternalStorage::doFetch( const QString &url, const QString &authConfig ) const
|
||||||
|
{
|
||||||
|
QgsFetchedContent *fetchedContent = QgsApplication::networkContentFetcherRegistry()->fetch( url, Qgis::ActionStart::Deferred, authConfig );
|
||||||
|
|
||||||
|
return new QgsHttpExternalStorageFetchedContent( fetchedContent );
|
||||||
|
}
|
||||||
///@endcond
|
///@endcond
|
@ -26,7 +26,7 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
class QgsFeedback;
|
class QgsFeedback;
|
||||||
class QgsWebDAVExternalStorageStoreTask;
|
class QgsHttpExternalStorageStoreTask;
|
||||||
class QgsFetchedContent;
|
class QgsFetchedContent;
|
||||||
|
|
||||||
///@cond PRIVATE
|
///@cond PRIVATE
|
||||||
@ -38,7 +38,7 @@ class QgsFetchedContent;
|
|||||||
*
|
*
|
||||||
* \since QGIS 3.22
|
* \since QGIS 3.22
|
||||||
*/
|
*/
|
||||||
class CORE_EXPORT QgsWebDAVExternalStorage : public QgsExternalStorage
|
class CORE_EXPORT QgsWebDavExternalStorage : public QgsExternalStorage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@ -55,17 +55,38 @@ class CORE_EXPORT QgsWebDAVExternalStorage : public QgsExternalStorage
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup core
|
* \ingroup core
|
||||||
* \brief Class for WebDAV stored content
|
* \brief External storage implementation using the protocol AWS S3.
|
||||||
|
*
|
||||||
|
* \since QGIS 3.28
|
||||||
|
*/
|
||||||
|
class CORE_EXPORT QgsAwsS3ExternalStorage : public QgsExternalStorage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
QString type() const override;
|
||||||
|
|
||||||
|
QString displayName() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
QgsExternalStorageStoredContent *doStore( const QString &filePath, const QString &url, const QString &authcfg = QString() ) const override;
|
||||||
|
|
||||||
|
QgsExternalStorageFetchedContent *doFetch( const QString &url, const QString &authConfig = QString() ) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup core
|
||||||
|
* \brief Class for HTTP stored content
|
||||||
*
|
*
|
||||||
* \since QGIS 3.22
|
* \since QGIS 3.22
|
||||||
*/
|
*/
|
||||||
class QgsWebDAVExternalStorageStoredContent : public QgsExternalStorageStoredContent
|
class QgsHttpExternalStorageStoredContent : public QgsExternalStorageStoredContent
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
QgsWebDAVExternalStorageStoredContent( const QString &filePath, const QString &url, const QString &authcfg = QString() );
|
QgsHttpExternalStorageStoredContent( const QString &filePath, const QString &url, const QString &authcfg = QString() );
|
||||||
|
|
||||||
void cancel() override;
|
void cancel() override;
|
||||||
|
|
||||||
@ -73,25 +94,28 @@ class QgsWebDAVExternalStorageStoredContent : public QgsExternalStorageStoredCo
|
|||||||
|
|
||||||
void store() override;
|
void store() override;
|
||||||
|
|
||||||
|
void setPrepareRequestHandler( std::function< void( QNetworkRequest &request, QFile *f ) > );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QPointer<QgsWebDAVExternalStorageStoreTask> mUploadTask;
|
std::function< void( QNetworkRequest &request, QFile *f ) > mPrepareRequestHandler = nullptr;
|
||||||
|
QPointer<QgsHttpExternalStorageStoreTask> mUploadTask;
|
||||||
QString mUrl;
|
QString mUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup core
|
* \ingroup core
|
||||||
* \brief Class for WebDAV fetched content
|
* \brief Class for HTTP fetched content
|
||||||
*
|
*
|
||||||
* \since QGIS 3.22
|
* \since QGIS 3.22
|
||||||
*/
|
*/
|
||||||
class QgsWebDAVExternalStorageFetchedContent : public QgsExternalStorageFetchedContent
|
class QgsHttpExternalStorageFetchedContent : public QgsExternalStorageFetchedContent
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
QgsWebDAVExternalStorageFetchedContent( QgsFetchedContent *fetchedContent );
|
QgsHttpExternalStorageFetchedContent( QgsFetchedContent *fetchedContent );
|
||||||
|
|
||||||
QString filePath() const override;
|
QString filePath() const override;
|
||||||
|
|
||||||
@ -111,17 +135,17 @@ class QgsWebDAVExternalStorageFetchedContent : public QgsExternalStorageFetchedC
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup core
|
* \ingroup core
|
||||||
* \brief Task to store a file to a given WebDAV url
|
* \brief Task to store a file to a given url
|
||||||
*
|
*
|
||||||
* \since QGIS 3.22
|
* \since QGIS 3.22
|
||||||
*/
|
*/
|
||||||
class QgsWebDAVExternalStorageStoreTask : public QgsTask
|
class QgsHttpExternalStorageStoreTask : public QgsTask
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
QgsWebDAVExternalStorageStoreTask( const QUrl &url, const QString &filePath, const QString &authCfg );
|
QgsHttpExternalStorageStoreTask( const QUrl &url, const QString &filePath, const QString &authCfg );
|
||||||
|
|
||||||
bool run() override;
|
bool run() override;
|
||||||
|
|
||||||
@ -129,8 +153,11 @@ class QgsWebDAVExternalStorageStoreTask : public QgsTask
|
|||||||
|
|
||||||
QString errorString() const;
|
QString errorString() const;
|
||||||
|
|
||||||
|
void setPrepareRequestHandler( std::function< void( QNetworkRequest &request, QFile *f ) > );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
std::function< void( QNetworkRequest &request, QFile *f ) > mPrepareRequestHandler = nullptr;
|
||||||
const QUrl mUrl;
|
const QUrl mUrl;
|
||||||
const QString mFilePath;
|
const QString mFilePath;
|
||||||
const QString mAuthCfg;
|
const QString mAuthCfg;
|
@ -89,7 +89,8 @@ ADD_PYTHON_TEST(PyQgsExpressionBuilderWidget test_qgsexpressionbuilderwidget.py)
|
|||||||
ADD_PYTHON_TEST(PyQgsExpressionLineEdit test_qgsexpressionlineedit.py)
|
ADD_PYTHON_TEST(PyQgsExpressionLineEdit test_qgsexpressionlineedit.py)
|
||||||
ADD_PYTHON_TEST(PyQgsExtentGroupBox test_qgsextentgroupbox.py)
|
ADD_PYTHON_TEST(PyQgsExtentGroupBox test_qgsextentgroupbox.py)
|
||||||
ADD_PYTHON_TEST(PyQgsExtentWidget test_qgsextentwidget.py)
|
ADD_PYTHON_TEST(PyQgsExtentWidget test_qgsextentwidget.py)
|
||||||
ADD_PYTHON_TEST(PyQgsExternalStorageWebDAV test_qgsexternalstorage_webdav.py)
|
ADD_PYTHON_TEST(PyQgsExternalStorageWebDav test_qgsexternalstorage_webdav.py)
|
||||||
|
ADD_PYTHON_TEST(PyQgsExternalStorageAwsS3 test_qgsexternalstorage_awss3.py)
|
||||||
ADD_PYTHON_TEST(PyQgsFeature test_qgsfeature.py)
|
ADD_PYTHON_TEST(PyQgsFeature test_qgsfeature.py)
|
||||||
ADD_PYTHON_TEST(PyQgsFeatureSink test_qgsfeaturesink.py)
|
ADD_PYTHON_TEST(PyQgsFeatureSink test_qgsfeaturesink.py)
|
||||||
ADD_PYTHON_TEST(PyQgsFeatureSource test_qgsfeaturesource.py)
|
ADD_PYTHON_TEST(PyQgsFeatureSource test_qgsfeaturesource.py)
|
||||||
|
67
tests/src/python/test_qgsexternalstorage_awss3.py
Normal file
67
tests/src/python/test_qgsexternalstorage_awss3.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""QGIS Unit tests for AWS S3 external storage
|
||||||
|
|
||||||
|
External storage backend must implement a test based on TestPyQgsExternalStorageBase
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
(at your option) any later version.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Jacky Volpes"
|
||||||
|
__date__ = "20/12/2022"
|
||||||
|
__copyright__ = "Copyright 2022, The QGIS Project"
|
||||||
|
|
||||||
|
from shutil import rmtree
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
|
||||||
|
from utilities import unitTestDataPath, waitServer
|
||||||
|
from test_qgsexternalstorage_base import TestPyQgsExternalStorageBase
|
||||||
|
|
||||||
|
from qgis.PyQt.QtCore import QCoreApplication, QEventLoop, QUrl
|
||||||
|
|
||||||
|
from qgis.core import (
|
||||||
|
QgsApplication,
|
||||||
|
QgsAuthMethodConfig,
|
||||||
|
QgsExternalStorageFetchedContent,
|
||||||
|
)
|
||||||
|
|
||||||
|
from qgis.testing import (
|
||||||
|
start_app,
|
||||||
|
unittest,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPyQgsExternalStorageAwsS3(TestPyQgsExternalStorageBase, unittest.TestCase):
|
||||||
|
|
||||||
|
storageType = "AWSS3"
|
||||||
|
badUrl = "http://nothinghere/"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""Run before all tests:"""
|
||||||
|
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
bucket_name = "test_bucket"
|
||||||
|
|
||||||
|
cls.auth_config = QgsAuthMethodConfig("AWSS3")
|
||||||
|
cls.auth_config.setConfig("username", "minioadmin")
|
||||||
|
cls.auth_config.setConfig("password", "adminio€")
|
||||||
|
cls.auth_config.setConfig("region", "us-east-1")
|
||||||
|
cls.auth_config.setName("test_awss3_auth_config")
|
||||||
|
assert cls.authm.storeAuthenticationConfig(cls.auth_config)[0]
|
||||||
|
assert cls.auth_config.isValid()
|
||||||
|
|
||||||
|
cls.url = "http://{}:{}/{}".format(
|
||||||
|
os.environ.get("QGIS_MINIO_HOST", "localhost"),
|
||||||
|
os.environ.get("QGIS_MINIO_PORT", "80"),
|
||||||
|
bucket_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
@ -9,9 +9,9 @@ the Free Software Foundation; either version 2 of the License, or
|
|||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = 'Julien Cabieces'
|
__author__ = "Julien Cabieces"
|
||||||
__date__ = '31/03/2021'
|
__date__ = "31/03/2021"
|
||||||
__copyright__ = 'Copyright 2021, The QGIS Project'
|
__copyright__ = "Copyright 2021, The QGIS Project"
|
||||||
|
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
import os
|
import os
|
||||||
@ -27,7 +27,8 @@ from qgis.core import (
|
|||||||
Qgis,
|
Qgis,
|
||||||
QgsApplication,
|
QgsApplication,
|
||||||
QgsAuthMethodConfig,
|
QgsAuthMethodConfig,
|
||||||
QgsExternalStorageFetchedContent)
|
QgsExternalStorageFetchedContent,
|
||||||
|
)
|
||||||
|
|
||||||
from qgis.testing import (
|
from qgis.testing import (
|
||||||
start_app,
|
start_app,
|
||||||
@ -35,7 +36,7 @@ from qgis.testing import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestPyQgsExternalStorageBase():
|
class TestPyQgsExternalStorageBase:
|
||||||
|
|
||||||
storageType = None
|
storageType = None
|
||||||
url = None
|
url = None
|
||||||
@ -50,13 +51,13 @@ class TestPyQgsExternalStorageBase():
|
|||||||
start_app()
|
start_app()
|
||||||
|
|
||||||
cls.authm = QgsApplication.authManager()
|
cls.authm = QgsApplication.authManager()
|
||||||
assert (cls.authm.setMasterPassword('masterpassword', True))
|
assert cls.authm.setMasterPassword("masterpassword", True)
|
||||||
assert not cls.authm.isDisabled(), cls.authm.disabledMessage()
|
assert not cls.authm.isDisabled(), cls.authm.disabledMessage()
|
||||||
|
|
||||||
cls.auth_config = QgsAuthMethodConfig("Basic")
|
cls.auth_config = QgsAuthMethodConfig("Basic")
|
||||||
cls.auth_config.setConfig('username', "qgis")
|
cls.auth_config.setConfig("username", "qgis")
|
||||||
cls.auth_config.setConfig('password', "myPasswd!")
|
cls.auth_config.setConfig("password", "myPasswd!")
|
||||||
cls.auth_config.setName('test_basic_auth_config')
|
cls.auth_config.setName("test_basic_auth_config")
|
||||||
assert cls.authm.storeAuthenticationConfig(cls.auth_config)[0]
|
assert cls.authm.storeAuthenticationConfig(cls.auth_config)[0]
|
||||||
assert cls.auth_config.isValid()
|
assert cls.auth_config.isValid()
|
||||||
|
|
||||||
@ -80,16 +81,21 @@ class TestPyQgsExternalStorageBase():
|
|||||||
"""Run after each test."""
|
"""Run after each test."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def getNewFile(self, content):
|
def getNewFile(self, content, with_special_characters=False):
|
||||||
"""Return a newly created temporary file with content"""
|
"""Return a newly created temporary file with content
|
||||||
f = tempfile.NamedTemporaryFile(suffix='.txt')
|
if with_special_characters is True then add url reserved characters in the file name"""
|
||||||
|
|
||||||
|
f = tempfile.NamedTemporaryFile(
|
||||||
|
suffix=".txt",
|
||||||
|
prefix="é~u!:;=\"',iù[ &²*k (~$£<" if with_special_characters else "",
|
||||||
|
)
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.flush()
|
f.flush()
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def checkContent(self, file_path, content):
|
def checkContent(self, file_path, content):
|
||||||
"""Check that file content matches given content"""
|
"""Check that file content matches given content"""
|
||||||
f = open(file_path, 'r')
|
f = open(file_path, "r")
|
||||||
self.assertTrue(f.read(), b"New content")
|
self.assertTrue(f.read(), b"New content")
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
@ -97,8 +103,10 @@ class TestPyQgsExternalStorageBase():
|
|||||||
"""
|
"""
|
||||||
Check that storage list in in correct order
|
Check that storage list in in correct order
|
||||||
"""
|
"""
|
||||||
self.assertEqual([storage.type() for storage in self.registry.externalStorages()],
|
self.assertEqual(
|
||||||
["SimpleCopy", "WebDAV"])
|
[storage.type() for storage in self.registry.externalStorages()],
|
||||||
|
["SimpleCopy", "WebDAV", "AWSS3"],
|
||||||
|
)
|
||||||
|
|
||||||
def testStoreFetchFileLater(self):
|
def testStoreFetchFileLater(self):
|
||||||
"""
|
"""
|
||||||
@ -130,7 +138,9 @@ class TestPyQgsExternalStorageBase():
|
|||||||
self.assertEqual(spyProgressChanged[-1][0], 100)
|
self.assertEqual(spyProgressChanged[-1][0], 100)
|
||||||
|
|
||||||
# fetch
|
# fetch
|
||||||
fetchedContent = self.storage.fetch(self.url + "/" + os.path.basename(f.name), self.auth_config.id())
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/" + os.path.basename(f.name), self.auth_config.id()
|
||||||
|
)
|
||||||
self.assertTrue(fetchedContent)
|
self.assertTrue(fetchedContent)
|
||||||
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.NotStarted)
|
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.NotStarted)
|
||||||
|
|
||||||
@ -147,10 +157,12 @@ class TestPyQgsExternalStorageBase():
|
|||||||
self.assertFalse(fetchedContent.errorString())
|
self.assertFalse(fetchedContent.errorString())
|
||||||
self.assertTrue(fetchedContent.filePath())
|
self.assertTrue(fetchedContent.filePath())
|
||||||
self.checkContent(fetchedContent.filePath(), b"New content")
|
self.checkContent(fetchedContent.filePath(), b"New content")
|
||||||
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], '.txt')
|
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], ".txt")
|
||||||
|
|
||||||
# fetch again, should be cached
|
# fetch again, should be cached
|
||||||
fetchedContent = self.storage.fetch(self.url + "/" + os.path.basename(f.name), self.auth_config.id())
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/" + os.path.basename(f.name), self.auth_config.id()
|
||||||
|
)
|
||||||
self.assertTrue(fetchedContent)
|
self.assertTrue(fetchedContent)
|
||||||
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.NotStarted)
|
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.NotStarted)
|
||||||
|
|
||||||
@ -167,7 +179,7 @@ class TestPyQgsExternalStorageBase():
|
|||||||
self.assertTrue(not fetchedContent.errorString())
|
self.assertTrue(not fetchedContent.errorString())
|
||||||
self.assertTrue(fetchedContent.filePath())
|
self.assertTrue(fetchedContent.filePath())
|
||||||
self.checkContent(fetchedContent.filePath(), b"New content")
|
self.checkContent(fetchedContent.filePath(), b"New content")
|
||||||
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], '.txt')
|
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], ".txt")
|
||||||
|
|
||||||
# fetch bad url
|
# fetch bad url
|
||||||
fetchedContent = self.storage.fetch(self.url + "/error", self.auth_config.id())
|
fetchedContent = self.storage.fetch(self.url + "/error", self.auth_config.id())
|
||||||
@ -187,16 +199,18 @@ class TestPyQgsExternalStorageBase():
|
|||||||
self.assertTrue(fetchedContent.errorString())
|
self.assertTrue(fetchedContent.errorString())
|
||||||
self.assertFalse(fetchedContent.filePath())
|
self.assertFalse(fetchedContent.filePath())
|
||||||
|
|
||||||
def testStoreFetchFileImmediately(self):
|
def testStoreFetchFileImmediatelySpecialCharacters(self):
|
||||||
"""
|
"""
|
||||||
Test file storing and fetching (Immediately mode)
|
Test file storing and fetching (Immediately mode) with special characters name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
f = self.getNewFile(b"New content")
|
f = self.getNewFile(b"New content", True)
|
||||||
|
|
||||||
# store
|
# store
|
||||||
url = self.url + "/" + os.path.basename(f.name)
|
url = self.url + "/" + os.path.basename(f.name)
|
||||||
storedContent = self.storage.store(f.name, url, self.auth_config.id(), Qgis.ActionStart.Immediate)
|
storedContent = self.storage.store(
|
||||||
|
f.name, url, self.auth_config.id(), Qgis.ActionStart.Immediate
|
||||||
|
)
|
||||||
self.assertTrue(storedContent)
|
self.assertTrue(storedContent)
|
||||||
self.assertEqual(storedContent.status(), Qgis.ContentStatus.Running)
|
self.assertEqual(storedContent.status(), Qgis.ContentStatus.Running)
|
||||||
|
|
||||||
@ -216,14 +230,20 @@ class TestPyQgsExternalStorageBase():
|
|||||||
self.assertEqual(spyProgressChanged[-1][0], 100)
|
self.assertEqual(spyProgressChanged[-1][0], 100)
|
||||||
|
|
||||||
# fetch
|
# fetch
|
||||||
fetchedContent = self.storage.fetch(self.url + "/" + os.path.basename(f.name), self.auth_config.id(), Qgis.ActionStart.Immediate)
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/" + os.path.basename(f.name),
|
||||||
|
self.auth_config.id(),
|
||||||
|
Qgis.ActionStart.Immediate,
|
||||||
|
)
|
||||||
self.assertTrue(fetchedContent)
|
self.assertTrue(fetchedContent)
|
||||||
|
|
||||||
# Some external storage (SimpleCopy) doesn't actually need to retrieve the resource
|
# Some external storage (SimpleCopy) doesn't actually need to retrieve the resource
|
||||||
self.assertTrue(fetchedContent.status() == Qgis.ContentStatus.Finished or
|
self.assertTrue(
|
||||||
fetchedContent.status() == Qgis.ContentStatus.Running)
|
fetchedContent.status() == Qgis.ContentStatus.Finished
|
||||||
|
or fetchedContent.status() == Qgis.ContentStatus.Running
|
||||||
|
)
|
||||||
|
|
||||||
if (fetchedContent.status() == Qgis.ContentStatus.Running):
|
if fetchedContent.status() == Qgis.ContentStatus.Running:
|
||||||
|
|
||||||
spyErrorOccurred = QSignalSpy(fetchedContent.errorOccurred)
|
spyErrorOccurred = QSignalSpy(fetchedContent.errorOccurred)
|
||||||
|
|
||||||
@ -238,27 +258,136 @@ class TestPyQgsExternalStorageBase():
|
|||||||
self.assertFalse(fetchedContent.errorString())
|
self.assertFalse(fetchedContent.errorString())
|
||||||
self.assertTrue(fetchedContent.filePath())
|
self.assertTrue(fetchedContent.filePath())
|
||||||
self.checkContent(fetchedContent.filePath(), b"New content")
|
self.checkContent(fetchedContent.filePath(), b"New content")
|
||||||
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], '.txt')
|
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], ".txt")
|
||||||
|
|
||||||
# fetch again, should be cached
|
# fetch again, should be cached
|
||||||
fetchedContent = self.storage.fetch(self.url + "/" + os.path.basename(f.name), self.auth_config.id(), Qgis.ActionStart.Immediate)
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/" + os.path.basename(f.name),
|
||||||
|
self.auth_config.id(),
|
||||||
|
Qgis.ActionStart.Immediate,
|
||||||
|
)
|
||||||
self.assertTrue(fetchedContent)
|
self.assertTrue(fetchedContent)
|
||||||
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.Finished)
|
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.Finished)
|
||||||
|
|
||||||
self.assertTrue(not fetchedContent.errorString())
|
self.assertTrue(not fetchedContent.errorString())
|
||||||
self.assertTrue(fetchedContent.filePath())
|
self.assertTrue(fetchedContent.filePath())
|
||||||
self.checkContent(fetchedContent.filePath(), b"New content")
|
self.checkContent(fetchedContent.filePath(), b"New content")
|
||||||
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], '.txt')
|
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], ".txt")
|
||||||
|
|
||||||
# fetch bad url
|
# fetch bad url
|
||||||
fetchedContent = self.storage.fetch(self.url + "/error", self.auth_config.id(), Qgis.ActionStart.Immediate)
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/error", self.auth_config.id(), Qgis.ActionStart.Immediate
|
||||||
|
)
|
||||||
self.assertTrue(fetchedContent)
|
self.assertTrue(fetchedContent)
|
||||||
|
|
||||||
# Some external storage (SimpleCopy) doesn't actually need to retrieve the resource
|
# Some external storage (SimpleCopy) doesn't actually need to retrieve the resource
|
||||||
self.assertTrue(fetchedContent.status() == Qgis.ContentStatus.Failed or
|
self.assertTrue(
|
||||||
fetchedContent.status() == Qgis.ContentStatus.Running)
|
fetchedContent.status() == Qgis.ContentStatus.Failed
|
||||||
|
or fetchedContent.status() == Qgis.ContentStatus.Running
|
||||||
|
)
|
||||||
|
|
||||||
if (fetchedContent.status() == Qgis.ContentStatus.Running):
|
if fetchedContent.status() == Qgis.ContentStatus.Running:
|
||||||
|
spyErrorOccurred = QSignalSpy(fetchedContent.errorOccurred)
|
||||||
|
|
||||||
|
loop = QEventLoop()
|
||||||
|
fetchedContent.errorOccurred.connect(loop.quit)
|
||||||
|
fetchedContent.fetched.connect(loop.quit)
|
||||||
|
loop.exec()
|
||||||
|
|
||||||
|
self.assertEqual(len(spyErrorOccurred), 1)
|
||||||
|
|
||||||
|
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.Failed)
|
||||||
|
self.assertTrue(fetchedContent.errorString())
|
||||||
|
self.assertFalse(fetchedContent.filePath())
|
||||||
|
|
||||||
|
def testStoreFetchFileImmediately(self):
|
||||||
|
"""
|
||||||
|
Test file storing and fetching (Immediately mode)
|
||||||
|
"""
|
||||||
|
|
||||||
|
f = self.getNewFile(b"New content")
|
||||||
|
|
||||||
|
# store
|
||||||
|
url = self.url + "/" + os.path.basename(f.name)
|
||||||
|
storedContent = self.storage.store(
|
||||||
|
f.name, url, self.auth_config.id(), Qgis.ActionStart.Immediate
|
||||||
|
)
|
||||||
|
self.assertTrue(storedContent)
|
||||||
|
self.assertEqual(storedContent.status(), Qgis.ContentStatus.Running)
|
||||||
|
|
||||||
|
spyErrorOccurred = QSignalSpy(storedContent.errorOccurred)
|
||||||
|
spyProgressChanged = QSignalSpy(storedContent.progressChanged)
|
||||||
|
|
||||||
|
loop = QEventLoop()
|
||||||
|
storedContent.stored.connect(loop.quit)
|
||||||
|
storedContent.errorOccurred.connect(loop.quit)
|
||||||
|
loop.exec()
|
||||||
|
|
||||||
|
self.assertEqual(len(spyErrorOccurred), 0)
|
||||||
|
self.assertEqual(storedContent.url(), url)
|
||||||
|
self.assertFalse(storedContent.errorString())
|
||||||
|
self.assertEqual(storedContent.status(), Qgis.ContentStatus.Finished)
|
||||||
|
self.assertTrue(len(spyProgressChanged) > 0)
|
||||||
|
self.assertEqual(spyProgressChanged[-1][0], 100)
|
||||||
|
|
||||||
|
# fetch
|
||||||
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/" + os.path.basename(f.name),
|
||||||
|
self.auth_config.id(),
|
||||||
|
Qgis.ActionStart.Immediate,
|
||||||
|
)
|
||||||
|
self.assertTrue(fetchedContent)
|
||||||
|
|
||||||
|
# Some external storage (SimpleCopy) doesn't actually need to retrieve the resource
|
||||||
|
self.assertTrue(
|
||||||
|
fetchedContent.status() == Qgis.ContentStatus.Finished
|
||||||
|
or fetchedContent.status() == Qgis.ContentStatus.Running
|
||||||
|
)
|
||||||
|
|
||||||
|
if fetchedContent.status() == Qgis.ContentStatus.Running:
|
||||||
|
|
||||||
|
spyErrorOccurred = QSignalSpy(fetchedContent.errorOccurred)
|
||||||
|
|
||||||
|
loop = QEventLoop()
|
||||||
|
fetchedContent.fetched.connect(loop.quit)
|
||||||
|
fetchedContent.errorOccurred.connect(loop.quit)
|
||||||
|
loop.exec()
|
||||||
|
|
||||||
|
self.assertEqual(len(spyErrorOccurred), 0)
|
||||||
|
|
||||||
|
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.Finished)
|
||||||
|
self.assertFalse(fetchedContent.errorString())
|
||||||
|
self.assertTrue(fetchedContent.filePath())
|
||||||
|
self.checkContent(fetchedContent.filePath(), b"New content")
|
||||||
|
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], ".txt")
|
||||||
|
|
||||||
|
# fetch again, should be cached
|
||||||
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/" + os.path.basename(f.name),
|
||||||
|
self.auth_config.id(),
|
||||||
|
Qgis.ActionStart.Immediate,
|
||||||
|
)
|
||||||
|
self.assertTrue(fetchedContent)
|
||||||
|
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.Finished)
|
||||||
|
|
||||||
|
self.assertTrue(not fetchedContent.errorString())
|
||||||
|
self.assertTrue(fetchedContent.filePath())
|
||||||
|
self.checkContent(fetchedContent.filePath(), b"New content")
|
||||||
|
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], ".txt")
|
||||||
|
|
||||||
|
# fetch bad url
|
||||||
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/error", self.auth_config.id(), Qgis.ActionStart.Immediate
|
||||||
|
)
|
||||||
|
self.assertTrue(fetchedContent)
|
||||||
|
|
||||||
|
# Some external storage (SimpleCopy) doesn't actually need to retrieve the resource
|
||||||
|
self.assertTrue(
|
||||||
|
fetchedContent.status() == Qgis.ContentStatus.Failed
|
||||||
|
or fetchedContent.status() == Qgis.ContentStatus.Running
|
||||||
|
)
|
||||||
|
|
||||||
|
if fetchedContent.status() == Qgis.ContentStatus.Running:
|
||||||
spyErrorOccurred = QSignalSpy(fetchedContent.errorOccurred)
|
spyErrorOccurred = QSignalSpy(fetchedContent.errorOccurred)
|
||||||
|
|
||||||
loop = QEventLoop()
|
loop = QEventLoop()
|
||||||
@ -278,7 +407,9 @@ class TestPyQgsExternalStorageBase():
|
|||||||
"""
|
"""
|
||||||
f = self.getNewFile(b"New content")
|
f = self.getNewFile(b"New content")
|
||||||
|
|
||||||
storedContent = self.storage.store(f.name, self.badUrl + os.path.basename(f.name), self.auth_config.id())
|
storedContent = self.storage.store(
|
||||||
|
f.name, self.badUrl + os.path.basename(f.name), self.auth_config.id()
|
||||||
|
)
|
||||||
self.assertTrue(storedContent)
|
self.assertTrue(storedContent)
|
||||||
self.assertEqual(storedContent.status(), Qgis.ContentStatus.NotStarted)
|
self.assertEqual(storedContent.status(), Qgis.ContentStatus.NotStarted)
|
||||||
|
|
||||||
@ -305,7 +436,9 @@ class TestPyQgsExternalStorageBase():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
f = self.getNewFile(b"New content")
|
f = self.getNewFile(b"New content")
|
||||||
storedContent = self.storage.store(f.name, self.url + "/" + os.path.basename(f.name))
|
storedContent = self.storage.store(
|
||||||
|
f.name, self.url + "/" + os.path.basename(f.name)
|
||||||
|
)
|
||||||
self.assertTrue(storedContent)
|
self.assertTrue(storedContent)
|
||||||
self.assertEqual(storedContent.status(), Qgis.ContentStatus.NotStarted)
|
self.assertEqual(storedContent.status(), Qgis.ContentStatus.NotStarted)
|
||||||
|
|
||||||
@ -334,7 +467,9 @@ class TestPyQgsExternalStorageBase():
|
|||||||
f = self.getNewFile(b"New content")
|
f = self.getNewFile(b"New content")
|
||||||
|
|
||||||
# store
|
# store
|
||||||
storedContent = self.storage.store(f.name, self.url + "/", self.auth_config.id())
|
storedContent = self.storage.store(
|
||||||
|
f.name, self.url + "/", self.auth_config.id()
|
||||||
|
)
|
||||||
self.assertTrue(storedContent)
|
self.assertTrue(storedContent)
|
||||||
self.assertEqual(storedContent.status(), Qgis.ContentStatus.NotStarted)
|
self.assertEqual(storedContent.status(), Qgis.ContentStatus.NotStarted)
|
||||||
|
|
||||||
@ -355,7 +490,9 @@ class TestPyQgsExternalStorageBase():
|
|||||||
self.assertEqual(spyProgressChanged[-1][0], 100)
|
self.assertEqual(spyProgressChanged[-1][0], 100)
|
||||||
|
|
||||||
# fetch
|
# fetch
|
||||||
fetchedContent = self.storage.fetch(self.url + "/" + os.path.basename(f.name), self.auth_config.id())
|
fetchedContent = self.storage.fetch(
|
||||||
|
self.url + "/" + os.path.basename(f.name), self.auth_config.id()
|
||||||
|
)
|
||||||
self.assertTrue(fetchedContent)
|
self.assertTrue(fetchedContent)
|
||||||
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.NotStarted)
|
self.assertEqual(fetchedContent.status(), Qgis.ContentStatus.NotStarted)
|
||||||
|
|
||||||
@ -372,4 +509,4 @@ class TestPyQgsExternalStorageBase():
|
|||||||
self.assertFalse(fetchedContent.errorString())
|
self.assertFalse(fetchedContent.errorString())
|
||||||
self.assertTrue(fetchedContent.filePath())
|
self.assertTrue(fetchedContent.filePath())
|
||||||
self.checkContent(fetchedContent.filePath(), b"New content")
|
self.checkContent(fetchedContent.filePath(), b"New content")
|
||||||
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], '.txt')
|
self.assertEqual(os.path.splitext(fetchedContent.filePath())[1], ".txt")
|
||||||
|
@ -34,7 +34,7 @@ from qgis.testing import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestPyQgsExternalStorageWebDAV(TestPyQgsExternalStorageBase, unittest.TestCase):
|
class TestPyQgsExternalStorageWebDav(TestPyQgsExternalStorageBase, unittest.TestCase):
|
||||||
|
|
||||||
storageType = "WebDAV"
|
storageType = "WebDAV"
|
||||||
badUrl = "http://nothinghere/"
|
badUrl = "http://nothinghere/"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user