From 0c4f2416dd0c7ee56420ac37d5062ddb7b184e3d Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Mon, 8 Dec 2025 12:55:43 +0200 Subject: [PATCH 01/14] Added initial draft for the auth db sync --- app/CMakeLists.txt | 2 + app/activeproject.cpp | 13 +++ app/authsync.cpp | 209 ++++++++++++++++++++++++++++++++++++++++++ app/authsync.h | 43 +++++++++ core/merginapi.cpp | 1 + 5 files changed, 268 insertions(+) create mode 100644 app/authsync.cpp create mode 100644 app/authsync.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index e49bfd223..19d7ab408 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -46,6 +46,7 @@ set(MM_SRCS activelayer.cpp activeproject.cpp androidutils.cpp + authsync.cpp mapsketchingcontroller.cpp appsettings.cpp autosynccontroller.cpp @@ -139,6 +140,7 @@ set(MM_HDRS mapsketchingcontroller.h appsettings.h autosynccontroller.h + authsync.h bluetoothdiscoverymodel.h qrcodedecoder.h changelogmodel.h diff --git a/app/activeproject.cpp b/app/activeproject.cpp index bbb52e893..8aec44fdd 100644 --- a/app/activeproject.cpp +++ b/app/activeproject.cpp @@ -20,6 +20,7 @@ #include "activeproject.h" #include "coreutils.h" +#include "authsync.h" #ifdef ANDROID #include "position/tracking/androidtrackingbroadcast.h" @@ -186,6 +187,18 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) QString role = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( mLocalProject.projectDir ) ).role; setProjectRole( role ); + if ( mLocalProject.isValid() ) + { + AuthSync authSync( mLocalProject.projectDir, mQgsProject ); + authSync.importAuth(); + } + + QString workspaceUuid = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( mLocalProject.projectDir ) ).projectId; + + if ( workspaceUuid != nullptr ) + { + } + updateMapTheme(); updateActiveLayer(); updateMapSettingsLayers(); diff --git a/app/authsync.cpp b/app/authsync.cpp new file mode 100644 index 000000000..a73619e78 --- /dev/null +++ b/app/authsync.cpp @@ -0,0 +1,209 @@ +#include "authsync.h" +#include +#include +#include + +const QString AUTH_CONFIG_FILENAME = "qgis_cfg.xml"; + +AuthSync::AuthSync( const QString &projectDir, QObject *parent ) + : QObject( parent ) + , mProject( QgsProject::instance() ) + , mProjectDir( projectDir ) + , mAuthMngr( QgsApplication::authManager() ) +{ + mAuthFile = QDir( mProjectDir ).filePath( AUTH_CONFIG_FILENAME ); +} + +QString AuthSync::getProjectUuid( const QString &projectDir ) const +{ + return MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir ) ).projectId; +} + +void AuthSync::logError( const QString &message ) const +{ + CoreUtils::log( "AuthSync", "ERROR: " + message ); +} + +void AuthSync::logInfo( const QString &message ) const +{ + CoreUtils::log( "AuthSync", "INFO: " + message ); +} + +void AuthSync::logWarning( const QString &message ) const +{ + CoreUtils::log( "AuthSync", "WARNING: " + message ); +} + + +QStringList AuthSync::getLayersAuthIds() const +{ + QStringList authIds; + QgsProviderRegistry *reg = QgsProviderRegistry::instance(); + + for ( QgsMapLayer *layer : mProject->mapLayers().values() ) + { + QString source = layer->source(); + QString provType = layer->providerType(); + QVariantMap decodedUri = reg->decodeUri( provType, source ); + + QString authId = decodedUri.value( "authcfg" ).toString(); + if ( !authId.isEmpty() ) + { + authIds += authId ; + } + } + return authIds; +} + +QString AuthSync::getAuthConfigHash( const QStringList &authIds ) const +{ + QStringList sortedIds = authIds; + std::sort( sortedIds.begin(), sortedIds.end() ); + + QCryptographicHash hasher( QCryptographicHash::Sha256 ); + + for ( const QString &authId : sortedIds ) + { + QgsAuthMethodConfig config; + // True to decrypt full details + if ( !mAuthMngr->loadAuthenticationConfig( authId, config, true ) ) + { + logError( QString( "Failed to load the authentication config for the auth ID: %1" ).arg( authId ) ); + continue; + } + + QString headerData = QString( "%1|%2|%3" ).arg( config.id(), config.method(), config.uri() ); + hasher.addData( headerData.toUtf8() ); + + QMap configMap = config.configMap(); + QStringList sortedKeys = configMap.keys(); + std::sort( sortedKeys.begin(), sortedKeys.end() ); + + for ( const QString &key : sortedKeys ) + { + QString entry = QString( "|%1=%2" ).arg( key, configMap.value( key ) ); + hasher.addData( entry.toUtf8() ); + } + } + + return QString( hasher.result().toHex() ); +} + +void AuthSync::exportAuth( const LocalProject &localProject ) +{ + QStringList authIds = getLayersAuthIds(); + if ( authIds.isEmpty() ) + return; + + if ( !mAuthMngr->masterPasswordIsSet() ) + { + logWarning( "Master Password not set. Cannot export auth configs." ); + QString msg = "Failed to export authentication configuration. If you want to share the credentials of the protected layer(s), set the master password please."; + QMessageBox::warning( + nullptr, "Cannot export configuration for protected layer", msg, QMessageBox::Close + ); + return; + } + + QString currentHash = getAuthConfigHash( authIds ); + QFile authFile( mAuthFile ); + bool fileExists = authFile.exists(); + bool shouldExport = true; + + if ( fileExists ) + { + if ( authFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) + { + QString content = authFile.readAll(); + authFile.close(); + + // Regex to find the HASH comment + QRegularExpression pattern( "" ); + QRegularExpressionMatch match = pattern.match( content ); + + if ( match.hasMatch() ) + { + QString existingHash = match.captured( 1 ); + if ( existingHash == currentHash ) + { + logInfo( "No change in auth config. No update needed." ); + shouldExport = false; + } + else + { + logInfo( "Auth config file change detected. Updating file..." ); + } + } + else + { + logWarning( "No hash found in existing config file. Creating one..." ); + } + } + else + { + logError( "Failed to open existing auth config file for reading." ); + } + } + + if ( !shouldExport ) + return; + + QString tempFile = QDir( mProjectDir ).filePath( QString( "temp_%1" ).arg( AUTH_CONFIG_FILENAME ) ); + QString projectId = getProjectUuid( mProjectDir ); + + bool success = mAuthMngr->exportAuthenticationConfigsToXml( tempFile, authIds, projectId ); + + if ( success ) + { + QFile temp( tempFile ); + if ( temp.open( QIODevice::ReadOnly | QIODevice::Text ) ) + { + QString xmlContent = temp.readAll(); + temp.close(); + + QString hashedContent = xmlContent + QString( "\n" ).arg( currentHash ); + + if ( authFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) ) + { + authFile.write( hashedContent.toUtf8() ); + authFile.close(); + } + else + { + logError( QString( "Failed to write exported hash content to file: %1" ).arg( mAuthFile ) ); + } + } + else + { + logError( QString( "Failed to read content from temporary export file: %1" ).arg( tempFile ) ); + } + + QFile::remove( tempFile ); + } + else + { + logError( QString( "Failed to export authentication configurations to XML: %1" ).arg( tempFile ) ); + } +} + + +void AuthSync::importAuth() +{ + QFile authFile( mAuthFile ); + + if ( authFile.exists() && QFileInfo::exists( mAuthFile ) ) + { + if ( !mAuthMngr->masterPasswordIsSet() ) + { + logWarning( "Master password is not set. Could not import auth config." ); + QString userMsg = "Could not import authentication configuration for the protected layer(s). Set the master password and reload the project if you want to access the protected layer(s)."; + QMessageBox::warning( nullptr, "Could not load protected layer", userMsg, QMessageBox::Close ); + return; + } + + QString projectId = getProjectUuid( mProjectDir ); + + bool ok = mAuthMngr->importAuthenticationConfigsFromXml( mAuthFile, projectId, true ); + logInfo( QString( "QGIS auth imported: %1" ).arg( ok ? "true" : "false" ) ); + } +} \ No newline at end of file diff --git a/app/authsync.h b/app/authsync.h new file mode 100644 index 000000000..817d69242 --- /dev/null +++ b/app/authsync.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * * + * 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 "qgsproject.h" +#include "qgsauthmanager.h" +#include "qgsapplication.h" +#include "qgsmaplayer.h" +#include "qgsproviderregistry.h" +#include "coreutils.h" +#include "activeproject.h" +#include "merginprojectmetadata.h" +#include "appsettings.h" + +class AuthSync : public QObject +{ + Q_OBJECT + + public: + explicit AuthSync( const QString &projectDir = QString(), QObject *parent = nullptr ); + + QStringList getLayersAuthIds() const; + QString getAuthConfigHash( const QStringList &authIds ) const; + void exportAuth( const LocalProject &localProject ); + void importAuth(); + + private: + QgsProject *mProject; + QString mProjectDir; + QString mAuthFile; + QgsAuthManager *mAuthMngr; + + + QString getProjectUuid( const QString &projectDir ) const; + void logError( const QString &message ) const; + void logInfo( const QString &message ) const ; + void logWarning( const QString &message ) const ; +}; \ No newline at end of file diff --git a/core/merginapi.cpp b/core/merginapi.cpp index e0824b6b1..c19de4afe 100644 --- a/core/merginapi.cpp +++ b/core/merginapi.cpp @@ -3677,6 +3677,7 @@ void MerginApi::finishProjectSync( const QString &projectFullName, bool syncSucc ProjectDiff diff = transaction.diff; int newVersion = syncSuccessful ? transaction.version : -1; + // TODO check if the xml configuration has been changed if ( transaction.gpkgSchemaChanged || projectFileHasBeenUpdated( diff ) ) { emit projectReloadNeededAfterSync( projectFullName ); From 0e9ccd6d0eaf4473d1e98126d408048d71d24170 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Tue, 9 Dec 2025 08:18:44 +0200 Subject: [PATCH 02/14] Modified authsync --- app/authsync.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/authsync.cpp b/app/authsync.cpp index a73619e78..4b90a56f8 100644 --- a/app/authsync.cpp +++ b/app/authsync.cpp @@ -2,6 +2,7 @@ #include #include #include +#include const QString AUTH_CONFIG_FILENAME = "qgis_cfg.xml"; @@ -195,10 +196,14 @@ void AuthSync::importAuth() { if ( !mAuthMngr->masterPasswordIsSet() ) { - logWarning( "Master password is not set. Could not import auth config." ); - QString userMsg = "Could not import authentication configuration for the protected layer(s). Set the master password and reload the project if you want to access the protected layer(s)."; - QMessageBox::warning( nullptr, "Could not load protected layer", userMsg, QMessageBox::Close ); - return; + bool isSet = mAuthMngr->setMasterPassword(true); + if(!isSet) + { + logWarning( "Master password is not set. Could not import auth config." ); + QString userMsg = "Could not import authentication configuration for the protected layer(s). Set the master password and reload the project if you want to access the protected layer(s)."; + QMessageBox::warning( nullptr, "Could not load protected layer", userMsg, QMessageBox::Close ); + + } } QString projectId = getProjectUuid( mProjectDir ); From 4f714111d81c7944a740287ae16edfe52ee60fe1 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Tue, 9 Dec 2025 09:33:41 +0200 Subject: [PATCH 03/14] Formatted code --- app/authsync.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/authsync.cpp b/app/authsync.cpp index 4b90a56f8..8af8f9e0a 100644 --- a/app/authsync.cpp +++ b/app/authsync.cpp @@ -196,14 +196,14 @@ void AuthSync::importAuth() { if ( !mAuthMngr->masterPasswordIsSet() ) { - bool isSet = mAuthMngr->setMasterPassword(true); - if(!isSet) + bool isSet = mAuthMngr->setMasterPassword( true ); + if ( !isSet ) { - logWarning( "Master password is not set. Could not import auth config." ); - QString userMsg = "Could not import authentication configuration for the protected layer(s). Set the master password and reload the project if you want to access the protected layer(s)."; - QMessageBox::warning( nullptr, "Could not load protected layer", userMsg, QMessageBox::Close ); - - } + logWarning( "Master password is not set. Could not import auth config." ); + QString userMsg = "Could not import authentication configuration for the protected layer(s). Set the master password and reload the project if you want to access the protected layer(s)."; + QMessageBox::warning( nullptr, "Could not load protected layer", userMsg, QMessageBox::Close ); + + } } QString projectId = getProjectUuid( mProjectDir ); From 414da05f4f05e677a0154b8ca8670e93eac45661 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Tue, 9 Dec 2025 10:26:44 +0200 Subject: [PATCH 04/14] Simplified class --- app/authsync.cpp | 172 +---------------------------------------------- app/authsync.h | 7 -- 2 files changed, 2 insertions(+), 177 deletions(-) diff --git a/app/authsync.cpp b/app/authsync.cpp index 8af8f9e0a..4d3da1c14 100644 --- a/app/authsync.cpp +++ b/app/authsync.cpp @@ -20,174 +20,6 @@ QString AuthSync::getProjectUuid( const QString &projectDir ) const return MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir ) ).projectId; } -void AuthSync::logError( const QString &message ) const -{ - CoreUtils::log( "AuthSync", "ERROR: " + message ); -} - -void AuthSync::logInfo( const QString &message ) const -{ - CoreUtils::log( "AuthSync", "INFO: " + message ); -} - -void AuthSync::logWarning( const QString &message ) const -{ - CoreUtils::log( "AuthSync", "WARNING: " + message ); -} - - -QStringList AuthSync::getLayersAuthIds() const -{ - QStringList authIds; - QgsProviderRegistry *reg = QgsProviderRegistry::instance(); - - for ( QgsMapLayer *layer : mProject->mapLayers().values() ) - { - QString source = layer->source(); - QString provType = layer->providerType(); - QVariantMap decodedUri = reg->decodeUri( provType, source ); - - QString authId = decodedUri.value( "authcfg" ).toString(); - if ( !authId.isEmpty() ) - { - authIds += authId ; - } - } - return authIds; -} - -QString AuthSync::getAuthConfigHash( const QStringList &authIds ) const -{ - QStringList sortedIds = authIds; - std::sort( sortedIds.begin(), sortedIds.end() ); - - QCryptographicHash hasher( QCryptographicHash::Sha256 ); - - for ( const QString &authId : sortedIds ) - { - QgsAuthMethodConfig config; - // True to decrypt full details - if ( !mAuthMngr->loadAuthenticationConfig( authId, config, true ) ) - { - logError( QString( "Failed to load the authentication config for the auth ID: %1" ).arg( authId ) ); - continue; - } - - QString headerData = QString( "%1|%2|%3" ).arg( config.id(), config.method(), config.uri() ); - hasher.addData( headerData.toUtf8() ); - - QMap configMap = config.configMap(); - QStringList sortedKeys = configMap.keys(); - std::sort( sortedKeys.begin(), sortedKeys.end() ); - - for ( const QString &key : sortedKeys ) - { - QString entry = QString( "|%1=%2" ).arg( key, configMap.value( key ) ); - hasher.addData( entry.toUtf8() ); - } - } - - return QString( hasher.result().toHex() ); -} - -void AuthSync::exportAuth( const LocalProject &localProject ) -{ - QStringList authIds = getLayersAuthIds(); - if ( authIds.isEmpty() ) - return; - - if ( !mAuthMngr->masterPasswordIsSet() ) - { - logWarning( "Master Password not set. Cannot export auth configs." ); - QString msg = "Failed to export authentication configuration. If you want to share the credentials of the protected layer(s), set the master password please."; - QMessageBox::warning( - nullptr, "Cannot export configuration for protected layer", msg, QMessageBox::Close - ); - return; - } - - QString currentHash = getAuthConfigHash( authIds ); - QFile authFile( mAuthFile ); - bool fileExists = authFile.exists(); - bool shouldExport = true; - - if ( fileExists ) - { - if ( authFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) - { - QString content = authFile.readAll(); - authFile.close(); - - // Regex to find the HASH comment - QRegularExpression pattern( "" ); - QRegularExpressionMatch match = pattern.match( content ); - - if ( match.hasMatch() ) - { - QString existingHash = match.captured( 1 ); - if ( existingHash == currentHash ) - { - logInfo( "No change in auth config. No update needed." ); - shouldExport = false; - } - else - { - logInfo( "Auth config file change detected. Updating file..." ); - } - } - else - { - logWarning( "No hash found in existing config file. Creating one..." ); - } - } - else - { - logError( "Failed to open existing auth config file for reading." ); - } - } - - if ( !shouldExport ) - return; - - QString tempFile = QDir( mProjectDir ).filePath( QString( "temp_%1" ).arg( AUTH_CONFIG_FILENAME ) ); - QString projectId = getProjectUuid( mProjectDir ); - - bool success = mAuthMngr->exportAuthenticationConfigsToXml( tempFile, authIds, projectId ); - - if ( success ) - { - QFile temp( tempFile ); - if ( temp.open( QIODevice::ReadOnly | QIODevice::Text ) ) - { - QString xmlContent = temp.readAll(); - temp.close(); - - QString hashedContent = xmlContent + QString( "\n" ).arg( currentHash ); - - if ( authFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) ) - { - authFile.write( hashedContent.toUtf8() ); - authFile.close(); - } - else - { - logError( QString( "Failed to write exported hash content to file: %1" ).arg( mAuthFile ) ); - } - } - else - { - logError( QString( "Failed to read content from temporary export file: %1" ).arg( tempFile ) ); - } - - QFile::remove( tempFile ); - } - else - { - logError( QString( "Failed to export authentication configurations to XML: %1" ).arg( tempFile ) ); - } -} - - void AuthSync::importAuth() { QFile authFile( mAuthFile ); @@ -199,7 +31,7 @@ void AuthSync::importAuth() bool isSet = mAuthMngr->setMasterPassword( true ); if ( !isSet ) { - logWarning( "Master password is not set. Could not import auth config." ); + CoreUtils::log("AuthSync manager", "Master password is not set. Could not import auth config." ); QString userMsg = "Could not import authentication configuration for the protected layer(s). Set the master password and reload the project if you want to access the protected layer(s)."; QMessageBox::warning( nullptr, "Could not load protected layer", userMsg, QMessageBox::Close ); @@ -209,6 +41,6 @@ void AuthSync::importAuth() QString projectId = getProjectUuid( mProjectDir ); bool ok = mAuthMngr->importAuthenticationConfigsFromXml( mAuthFile, projectId, true ); - logInfo( QString( "QGIS auth imported: %1" ).arg( ok ? "true" : "false" ) ); + CoreUtils::log( "AuthSync manager", QString( "QGIS auth imported: %1" ).arg( ok ? "true" : "false" ) ); } } \ No newline at end of file diff --git a/app/authsync.h b/app/authsync.h index 817d69242..45f0eb850 100644 --- a/app/authsync.h +++ b/app/authsync.h @@ -23,10 +23,6 @@ class AuthSync : public QObject public: explicit AuthSync( const QString &projectDir = QString(), QObject *parent = nullptr ); - - QStringList getLayersAuthIds() const; - QString getAuthConfigHash( const QStringList &authIds ) const; - void exportAuth( const LocalProject &localProject ); void importAuth(); private: @@ -37,7 +33,4 @@ class AuthSync : public QObject QString getProjectUuid( const QString &projectDir ) const; - void logError( const QString &message ) const; - void logInfo( const QString &message ) const ; - void logWarning( const QString &message ) const ; }; \ No newline at end of file From 8a09c9549db44727849dab0dbf97d31779ed9f86 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Tue, 9 Dec 2025 10:27:02 +0200 Subject: [PATCH 05/14] Formatted code --- app/authsync.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/authsync.cpp b/app/authsync.cpp index 4d3da1c14..c1b9c6ca8 100644 --- a/app/authsync.cpp +++ b/app/authsync.cpp @@ -31,7 +31,7 @@ void AuthSync::importAuth() bool isSet = mAuthMngr->setMasterPassword( true ); if ( !isSet ) { - CoreUtils::log("AuthSync manager", "Master password is not set. Could not import auth config." ); + CoreUtils::log( "AuthSync manager", "Master password is not set. Could not import auth config." ); QString userMsg = "Could not import authentication configuration for the protected layer(s). Set the master password and reload the project if you want to access the protected layer(s)."; QMessageBox::warning( nullptr, "Could not load protected layer", userMsg, QMessageBox::Close ); From ad75563ce26dc6c1f4c07c7ca2bbf7c7cf5847f1 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Wed, 10 Dec 2025 10:52:48 +0200 Subject: [PATCH 06/14] Qca-workaround --- app/CMakeLists.txt | 19 +++++++++++++++++++ app/main.cpp | 2 ++ 2 files changed, 21 insertions(+) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 19d7ab408..eadd2d256 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -508,10 +508,29 @@ if (LNX) target_link_libraries(MerginMaps PUBLIC QGIS::Core) endif () +# We search in 'lib/Qca/crypto' and 'plugins/crypto' +find_library(QCA_OSSL_PLUGIN_LIB + NAMES qca-ossl libqca-ossl + HINTS ${CMAKE_PREFIX_PATH} + PATH_SUFFIXES + lib/Qca/crypto + lib/crypto + plugins/crypto + Qca/crypto + NO_DEFAULT_PATH +) + +if (QCA_OSSL_PLUGIN_LIB) + message(STATUS "Found QCA OpenSSL Plugin: ${QCA_OSSL_PLUGIN_LIB}") +else() + message(WARNING "Could not find 'qca-ossl'. Authentication might fail.") +endif() + target_link_libraries( MerginMaps PUBLIC Qt6Keychain::Qt6Keychain qca + ${QCA_OSSL_PLUGIN_LIB} GDAL::GDAL PostgreSQL::PostgreSQL Spatialite::Spatialite diff --git a/app/main.cpp b/app/main.cpp index 2e0bb1bfe..2f73a086a 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -155,6 +155,8 @@ #include "qgsapplication.h" #include "activeproject.h" #include "appsettings.h" +#include +Q_IMPORT_PLUGIN(opensslPlugin) static QString getDataDir() { From 9565366b62d04f6d52318e8af42685058152c40f Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 12 Dec 2025 09:44:39 +0200 Subject: [PATCH 07/14] Modified auth db sync Changed logic in auth sync --- app/activeproject.cpp | 15 ++++----------- app/authsync.cpp | 32 ++++++++++++++++++-------------- app/authsync.h | 1 + app/main.cpp | 2 +- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/app/activeproject.cpp b/app/activeproject.cpp index 8aec44fdd..e58825221 100644 --- a/app/activeproject.cpp +++ b/app/activeproject.cpp @@ -156,6 +156,10 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) emit projectWillBeReloaded(); mActiveLayer.resetActiveLayer(); + // import authentication database before loading the layers + AuthSync authSync( mLocalProjectsManager.projectFromProjectFilePath( filePath ).projectDir, mQgsProject ); + authSync.importAuth(); + res = mQgsProject->read( filePath ); if ( !res ) { @@ -187,17 +191,6 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) QString role = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( mLocalProject.projectDir ) ).role; setProjectRole( role ); - if ( mLocalProject.isValid() ) - { - AuthSync authSync( mLocalProject.projectDir, mQgsProject ); - authSync.importAuth(); - } - - QString workspaceUuid = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( mLocalProject.projectDir ) ).projectId; - - if ( workspaceUuid != nullptr ) - { - } updateMapTheme(); updateActiveLayer(); diff --git a/app/authsync.cpp b/app/authsync.cpp index c1b9c6ca8..35914bd9a 100644 --- a/app/authsync.cpp +++ b/app/authsync.cpp @@ -20,23 +20,27 @@ QString AuthSync::getProjectUuid( const QString &projectDir ) const return MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir ) ).projectId; } -void AuthSync::importAuth() +bool AuthSync::fileExists( QString path ) { - QFile authFile( mAuthFile ); + QFileInfo check_file( path ); + // check if path exists and if yes: Is it really a file and not a directory + if ( check_file.exists() || check_file.isFile() ) + { + return check_file.isWritable(); + } + else + { + return false; + } +} - if ( authFile.exists() && QFileInfo::exists( mAuthFile ) ) +void AuthSync::importAuth() +{ + if ( fileExists( mAuthFile ) ) { - if ( !mAuthMngr->masterPasswordIsSet() ) - { - bool isSet = mAuthMngr->setMasterPassword( true ); - if ( !isSet ) - { - CoreUtils::log( "AuthSync manager", "Master password is not set. Could not import auth config." ); - QString userMsg = "Could not import authentication configuration for the protected layer(s). Set the master password and reload the project if you want to access the protected layer(s)."; - QMessageBox::warning( nullptr, "Could not load protected layer", userMsg, QMessageBox::Close ); - - } - } + + mAuthMngr->setPasswordHelperEnabled( false ); + mAuthMngr->setMasterPassword( QStringLiteral( "merginMaps" ) ); QString projectId = getProjectUuid( mProjectDir ); diff --git a/app/authsync.h b/app/authsync.h index 45f0eb850..425049a94 100644 --- a/app/authsync.h +++ b/app/authsync.h @@ -24,6 +24,7 @@ class AuthSync : public QObject public: explicit AuthSync( const QString &projectDir = QString(), QObject *parent = nullptr ); void importAuth(); + bool fileExists( QString path ); private: QgsProject *mProject; diff --git a/app/main.cpp b/app/main.cpp index 2f73a086a..4b11c4ad6 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -156,7 +156,7 @@ #include "activeproject.h" #include "appsettings.h" #include -Q_IMPORT_PLUGIN(opensslPlugin) +Q_IMPORT_PLUGIN( opensslPlugin ) static QString getDataDir() { From c5c15c73276454f26979a9c0cfb6213c43a4fc2c Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 12 Dec 2025 10:27:39 +0200 Subject: [PATCH 08/14] Formatted cmake file --- app/CMakeLists.txt | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index eadd2d256..9f4b4378b 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -508,23 +508,17 @@ if (LNX) target_link_libraries(MerginMaps PUBLIC QGIS::Core) endif () -# We search in 'lib/Qca/crypto' and 'plugins/crypto' -find_library(QCA_OSSL_PLUGIN_LIB - NAMES qca-ossl libqca-ossl - HINTS ${CMAKE_PREFIX_PATH} - PATH_SUFFIXES - lib/Qca/crypto - lib/crypto - plugins/crypto - Qca/crypto - NO_DEFAULT_PATH +find_library( + QCA_OSSL_PLUGIN_LIB + NAMES qca-ossl + PATH_SUFFIXES lib/Qca/crypto ) if (QCA_OSSL_PLUGIN_LIB) - message(STATUS "Found QCA OpenSSL Plugin: ${QCA_OSSL_PLUGIN_LIB}") -else() - message(WARNING "Could not find 'qca-ossl'. Authentication might fail.") -endif() + message(STATUS "Found QCA OpenSSL Plugin: ${QCA_OSSL_PLUGIN_LIB}") +else () + message(WARNING "Could not find 'qca-ossl'. Authentication might fail.") +endif () target_link_libraries( MerginMaps From adeefbdaf0d0b61437ab8067e6d1be62873c0f75 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 12 Dec 2025 12:59:47 +0200 Subject: [PATCH 09/14] Implemented code review findings --- app/authsync.cpp | 9 +-------- app/authsync.h | 5 ----- app/main.cpp | 7 +++++++ core/merginapi.cpp | 1 - 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/authsync.cpp b/app/authsync.cpp index 35914bd9a..42402ae8d 100644 --- a/app/authsync.cpp +++ b/app/authsync.cpp @@ -1,8 +1,5 @@ #include "authsync.h" #include -#include -#include -#include const QString AUTH_CONFIG_FILENAME = "qgis_cfg.xml"; @@ -23,7 +20,7 @@ QString AuthSync::getProjectUuid( const QString &projectDir ) const bool AuthSync::fileExists( QString path ) { QFileInfo check_file( path ); - // check if path exists and if yes: Is it really a file and not a directory + // check if path exists and it is indeed a file if ( check_file.exists() || check_file.isFile() ) { return check_file.isWritable(); @@ -38,10 +35,6 @@ void AuthSync::importAuth() { if ( fileExists( mAuthFile ) ) { - - mAuthMngr->setPasswordHelperEnabled( false ); - mAuthMngr->setMasterPassword( QStringLiteral( "merginMaps" ) ); - QString projectId = getProjectUuid( mProjectDir ); bool ok = mAuthMngr->importAuthenticationConfigsFromXml( mAuthFile, projectId, true ); diff --git a/app/authsync.h b/app/authsync.h index 425049a94..3e4fb8b25 100644 --- a/app/authsync.h +++ b/app/authsync.h @@ -10,12 +10,8 @@ #include "qgsproject.h" #include "qgsauthmanager.h" #include "qgsapplication.h" -#include "qgsmaplayer.h" -#include "qgsproviderregistry.h" #include "coreutils.h" -#include "activeproject.h" #include "merginprojectmetadata.h" -#include "appsettings.h" class AuthSync : public QObject { @@ -32,6 +28,5 @@ class AuthSync : public QObject QString mAuthFile; QgsAuthManager *mAuthMngr; - QString getProjectUuid( const QString &projectDir ) const; }; \ No newline at end of file diff --git a/app/main.cpp b/app/main.cpp index 4b11c4ad6..889ff7a81 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -27,6 +27,7 @@ #include "test/inputtests.h" #endif #include +#include "qgsauthmanager.h" #include #include "qgsconfig.h" #include "qgsproviderregistry.h" @@ -545,6 +546,12 @@ int main( int argc, char *argv[] ) LayerTreeFlatModelPixmapProvider *layerTreeFlatModelPixmapProvider( new LayerTreeFlatModelPixmapProvider ); LayerDetailLegendImageProvider *layerDetailLegendImageProvider( new LayerDetailLegendImageProvider ); + // setting up the master password for authentication database retrieval + QgsAuthManager *authManager = QgsApplication::authManager(); + authManager->setPasswordHelperEnabled( false ); + authManager->setMasterPassword( QStringLiteral( "merginMaps" ) ); + + // build position kit, save active provider to QSettings and load previously active provider PositionKit pk; QObject::connect( &pk, &PositionKit::positionProviderChanged, as, [as]( AbstractPositionProvider * provider ) diff --git a/core/merginapi.cpp b/core/merginapi.cpp index c19de4afe..e0824b6b1 100644 --- a/core/merginapi.cpp +++ b/core/merginapi.cpp @@ -3677,7 +3677,6 @@ void MerginApi::finishProjectSync( const QString &projectFullName, bool syncSucc ProjectDiff diff = transaction.diff; int newVersion = syncSuccessful ? transaction.version : -1; - // TODO check if the xml configuration has been changed if ( transaction.gpkgSchemaChanged || projectFileHasBeenUpdated( diff ) ) { emit projectReloadNeededAfterSync( projectFullName ); From 017f4d788b4656494af7d50b850334d2da514707 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 12 Dec 2025 15:33:23 +0200 Subject: [PATCH 10/14] Removed authSync class + header Added unit tests to handle the import of configuration file --- app/CMakeLists.txt | 4 +- app/activeproject.cpp | 14 ++- app/authsync.cpp | 43 ------- app/authsync.h | 32 ------ app/test/inputtests.cpp | 6 + app/test/testauthimport.cpp | 168 ++++++++++++++++++++++++++++ app/test/testauthimport.h | 57 ++++++++++ test/CMakeLists.txt | 1 + test/test_data/test_auth_config.xml | 4 + 9 files changed, 249 insertions(+), 80 deletions(-) delete mode 100644 app/authsync.cpp delete mode 100644 app/authsync.h create mode 100644 app/test/testauthimport.cpp create mode 100644 app/test/testauthimport.h create mode 100644 test/test_data/test_auth_config.xml diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 9f4b4378b..782a76383 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -46,7 +46,6 @@ set(MM_SRCS activelayer.cpp activeproject.cpp androidutils.cpp - authsync.cpp mapsketchingcontroller.cpp appsettings.cpp autosynccontroller.cpp @@ -140,7 +139,6 @@ set(MM_HDRS mapsketchingcontroller.h appsettings.h autosynccontroller.h - authsync.h bluetoothdiscoverymodel.h qrcodedecoder.h changelogmodel.h @@ -206,6 +204,7 @@ if (ENABLE_TESTS) test/testlinks.cpp test/testmaptools.cpp test/testmerginapi.cpp + test/testauthimport.cpp test/testmodels.cpp test/testposition.cpp test/testrememberattributescontroller.cpp @@ -232,6 +231,7 @@ if (ENABLE_TESTS) test/testlinks.h test/testmaptools.h test/testmerginapi.h + test/testauthimport.cpp test/testmodels.h test/testposition.h test/testrememberattributescontroller.h diff --git a/app/activeproject.cpp b/app/activeproject.cpp index e58825221..d3543a73e 100644 --- a/app/activeproject.cpp +++ b/app/activeproject.cpp @@ -17,15 +17,18 @@ #include "qgslayertreelayer.h" #include "qgslayertreegroup.h" #include "qgsmapthemecollection.h" +#include "qgsauthmanager.h" +#include "qgsapplication.h" #include "activeproject.h" #include "coreutils.h" -#include "authsync.h" #ifdef ANDROID #include "position/tracking/androidtrackingbroadcast.h" #endif +const QString AUTH_CONFIG_FILENAME = "qgis_cfg.xml"; + const QString ActiveProject::LOADING_FLAG_FILE_PATH = QString( "%1/.input_loading_project" ).arg( QStandardPaths::standardLocations( QStandardPaths::TempLocation ).first() ); const int ActiveProject::LOADING_FLAG_FILE_EXPIRATION_MS = 5000; @@ -157,8 +160,13 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) mActiveLayer.resetActiveLayer(); // import authentication database before loading the layers - AuthSync authSync( mLocalProjectsManager.projectFromProjectFilePath( filePath ).projectDir, mQgsProject ); - authSync.importAuth(); + QgsAuthManager *authMngr = QgsApplication::authManager(); + QString projectDir = mLocalProjectsManager.projectFromProjectFilePath( filePath ).projectDir ; + QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); + QString projectId = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir ) ).projectId; + + bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, projectId, true ); + CoreUtils::log( "AuthSync manager", QString( "QGIS auth imported: %1" ).arg( ok ? "true" : "false" ) ); res = mQgsProject->read( filePath ); if ( !res ) diff --git a/app/authsync.cpp b/app/authsync.cpp deleted file mode 100644 index 42402ae8d..000000000 --- a/app/authsync.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "authsync.h" -#include - -const QString AUTH_CONFIG_FILENAME = "qgis_cfg.xml"; - -AuthSync::AuthSync( const QString &projectDir, QObject *parent ) - : QObject( parent ) - , mProject( QgsProject::instance() ) - , mProjectDir( projectDir ) - , mAuthMngr( QgsApplication::authManager() ) -{ - mAuthFile = QDir( mProjectDir ).filePath( AUTH_CONFIG_FILENAME ); -} - -QString AuthSync::getProjectUuid( const QString &projectDir ) const -{ - return MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir ) ).projectId; -} - -bool AuthSync::fileExists( QString path ) -{ - QFileInfo check_file( path ); - // check if path exists and it is indeed a file - if ( check_file.exists() || check_file.isFile() ) - { - return check_file.isWritable(); - } - else - { - return false; - } -} - -void AuthSync::importAuth() -{ - if ( fileExists( mAuthFile ) ) - { - QString projectId = getProjectUuid( mProjectDir ); - - bool ok = mAuthMngr->importAuthenticationConfigsFromXml( mAuthFile, projectId, true ); - CoreUtils::log( "AuthSync manager", QString( "QGIS auth imported: %1" ).arg( ok ? "true" : "false" ) ); - } -} \ No newline at end of file diff --git a/app/authsync.h b/app/authsync.h deleted file mode 100644 index 3e4fb8b25..000000000 --- a/app/authsync.h +++ /dev/null @@ -1,32 +0,0 @@ -/*************************************************************************** - * * - * 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 "qgsproject.h" -#include "qgsauthmanager.h" -#include "qgsapplication.h" -#include "coreutils.h" -#include "merginprojectmetadata.h" - -class AuthSync : public QObject -{ - Q_OBJECT - - public: - explicit AuthSync( const QString &projectDir = QString(), QObject *parent = nullptr ); - void importAuth(); - bool fileExists( QString path ); - - private: - QgsProject *mProject; - QString mProjectDir; - QString mAuthFile; - QgsAuthManager *mAuthMngr; - - QString getProjectUuid( const QString &projectDir ) const; -}; \ No newline at end of file diff --git a/app/test/inputtests.cpp b/app/test/inputtests.cpp index 282f9c99c..92f7f56b8 100644 --- a/app/test/inputtests.cpp +++ b/app/test/inputtests.cpp @@ -11,6 +11,7 @@ #include "testsketching.h" #include "testmerginapi.h" +#include "testauthimport.h" #include "testlinks.h" #include "testutilsfunctions.h" @@ -205,6 +206,11 @@ int InputTests::runTest() const TestMultiEditManager multiEditManagerTest; nFailed = QTest::qExec( &multiEditManagerTest, mTestArgs ); } + else if ( mTestRequested == "--testAuthImport" ) + { + TestAuthImport authImportTest; + nFailed = QTest::qExec( &authImportTest, mTestArgs ); + } else { qDebug() << "invalid test requested" << mTestRequested; diff --git a/app/test/testauthimport.cpp b/app/test/testauthimport.cpp new file mode 100644 index 000000000..ed8de57fa --- /dev/null +++ b/app/test/testauthimport.cpp @@ -0,0 +1,168 @@ +#include "testauthimport.h" +#include "testutils.h" +#include + +TestAuthImport::TestAuthImport() + : QObject( nullptr ) + , mPass( "1234" ) +{ +} + +void TestAuthImport::initTestCase() +{ + if ( !QgsApplication::instance() ) + { + static int argc = 1; + static char *argv[] = { ( char * )"test_app", nullptr }; + new QgsApplication( argc, argv, true, "TestApp" ); + } + + // create a temporary folder + mAppTempDir.reset( new QTemporaryDir() ); + QVERIFY( mAppTempDir->isValid() ); + qputenv( "QGIS_AUTH_DB_DIR_PATH", mAppTempDir->path().toLatin1() ); + + // init QGIS + QgsApplication::initQgis(); + + // check that qca is correctly linked and the authentication system is not disabled + QVERIFY2( !QgsApplication::authManager()->isDisabled(), + "Authentication system is DISABLED. Check QCA/OpenSSL dependency deployment." ); +} + +void TestAuthImport::cleanupTestCase() +{ + QgsApplication::authManager()->clearMasterPassword(); + QgsApplication::authManager()->removeAllAuthenticationConfigs(); + mAppTempDir.reset(); + QgsApplication::exitQgis(); +} + +void TestAuthImport::cleanup() +{ + // Clear configs after each test to ensure isolation + QgsApplication::authManager()->removeAllAuthenticationConfigs(); +} + +void TestAuthImport::mockProjectMetadata( const QString &projectDir ) +{ + // method to create a mock .mergin/metadata.json file with TEST_PROJECT_ID + QString metadataPath = CoreUtils::getProjectMetadataPath( projectDir ); + QDir().mkpath( QFileInfo( metadataPath ).path() ); + + QFile file( metadataPath ); + if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) ) + { + QTextStream stream( &file ); + stream << "{\"projectId\":\"" << TEST_PROJECT_ID << "\"}"; + file.close(); + QVERIFY( true ); + } +} + +void TestAuthImport::copyTestAuthFile( const QString &projectDir ) +{ + const QString sourcePath = TestUtils::testDataDir() + "/" + ( TEST_XML_FILENAME ); + const QString destPath = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); + + QVERIFY2( QFile::exists( sourcePath ), + qPrintable( QString( "Source XML file missing: %1" ).arg( sourcePath ) ) ); + + // copy the pre-built, encrypted cfg XML file into the temporary project directory + QVERIFY2( QFile::copy( sourcePath, destPath ), + qPrintable( QString( "Failed to copy test XML from %1 to %2" ).arg( sourcePath, destPath ) ) ); +} + +void TestAuthImport::test_successful_of_authFile() +{ + // authentication DB config file is present. Should succeed. + + // setup project dir, mock metadata, and copy the encrypted XML file + QTemporaryDir projectTempDir; + const QString projectDir = projectTempDir.path(); + mockProjectMetadata( projectDir ); + copyTestAuthFile( projectDir ); + + // initialise the QGIS authentication manager + QgsAuthManager *authMngr = QgsApplication::authManager(); + QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); + + bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); + + QVERIFY2( ok, "Importing auth config XML failed when Master Password was set." ); +} + +void TestAuthImport::test_import_fails_authFile_missing() +{ + // authentication DB config fileis missing, negative test case. + + // setup project dir and mock metadata (NO call to copyTestAuthFile) + QTemporaryDir projectTempDir; + const QString projectDir = projectTempDir.path(); + mockProjectMetadata( projectDir ); + + // initialise the QGIS authentication manager + QgsAuthManager *authMngr = QgsApplication::authManager(); + QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); + + bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); + + // import must fail as the auth manager fails to open the file + QVERIFY2( !ok, "Importing should have failed because the auth file was missing." ); + + // no configs should have been loaded + QCOMPARE( authMngr->configIds().count(), 0 ); +} + +void TestAuthImport::test_import_fails_master_password_not_set() +{ + // authentication DB config file is present, master Password is NOT set, import must fail + + // setup project dir, mock metadata, and copy the encrypted XML file + QTemporaryDir projectTempDir; + const QString projectDir = projectTempDir.path(); + mockProjectMetadata( projectDir ); + copyTestAuthFile( projectDir ); + + // make sure that the master Password is NOT set + QgsAuthManager *authMngr = QgsApplication::authManager(); + authMngr->clearMasterPassword(); + QVERIFY( !authMngr->masterPasswordIsSet() ); + + QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); + bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); + + // the import method does not return false if no master password is set + // however it does not load any configurations, thus the import failed + QVERIFY2( ok, "Importing should have failed because the Master Password was not set." ); + QCOMPARE( authMngr->configIds().count(), 0 ); + + // we set master password again so that the following test does not fail + QVERIFY( authMngr->setMasterPassword( QStringLiteral( "merginMaps" ), true ) ); + QVERIFY( authMngr->masterPasswordIsSet() ); +} + +void TestAuthImport::test_reload_after_configuration_update() +{ + // import works, then reload works, as overwrite=true flag is effective, when the cfg xml file is updated + + // setup project dir, mock metadata, copy XML, and set Master Password + QTemporaryDir projectTempDir; + const QString projectDir = projectTempDir.path(); + mockProjectMetadata( projectDir ); + copyTestAuthFile( projectDir ); + + QgsAuthManager *authMngr = QgsApplication::authManager(); + QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); + + QVERIFY( authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ) ); + QCOMPARE( authMngr->configIds().count(), 1 ); + + // run the import call again, which happends after the configuration file has been resynchronised + bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); + + QVERIFY2( ok, "Reloading/overwriting the existing auth config failed." ); + + // the number of configurations should remain 1 (it was overwritten/refreshed) + QCOMPARE( authMngr->configIds().count(), 1 ); +} \ No newline at end of file diff --git a/app/test/testauthimport.h b/app/test/testauthimport.h new file mode 100644 index 000000000..4e4e7768d --- /dev/null +++ b/app/test/testauthimport.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * * + * 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 TESTAUTHIMPORT_H +#define TESTAUTHIMPORT_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "coreutils.h" +#include "merginprojectmetadata.h" +#include "localprojectsmanager.h" + +const QString AUTH_CONFIG_FILENAME = "qgis_auth.xml"; +const QString TEST_PROJECT_ID = "mobile_test_auth_1234"; +const QString TEST_AUTH_ID = "mobile_test_config_id"; +const QString TEST_XML_FILENAME = "test_auth_config.xml"; + +class TestAuthImport : public QObject +{ + Q_OBJECT + + public: + TestAuthImport(); + + private slots: + void initTestCase(); + void cleanupTestCase(); + void cleanup(); + + void test_successful_of_authFile(); + void test_import_fails_authFile_missing(); + void test_import_fails_master_password_not_set(); + void test_reload_after_configuration_update(); + + private: + std::unique_ptr mAppTempDir; + const char *mPass = nullptr; + + void mockProjectMetadata( const QString &projectDir ); + void setup_ExportInitialConfig( const QString &projectDir, const QString &authId, const QString &username ); + void copyTestAuthFile( const QString &projectDir ); +}; + +#endif // TESTAUTHIMPORT_H \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e6cb6461..70f906675 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,6 +10,7 @@ set(MM_TESTS testLinks testUtils testAttributePreviewController + testAuthImport testMerginApi testAttributeController testIdentifyKit diff --git a/test/test_data/test_auth_config.xml b/test/test_data/test_auth_config.xml new file mode 100644 index 000000000..046800f3d --- /dev/null +++ b/test/test_data/test_auth_config.xml @@ -0,0 +1,4 @@ + +603f9a4b796e6b521f45155aa2ec6d8c0cb20f6b4290bd79ea60df7917fa1ac75b8bd929ff3ff48de46d6def199f86c0e3d1bd9e671b866e911ed0e978bc0fa1ddd3adcb68a6c871e146acf6b91a2d38b54e108c962861983e2d7097a2afe0c99ea979efcdb02e4f8d42a45a94ff38c45726614e7a136926f95a267531fe2ed43a4b7e2a0a758811c1bdff9a39152eebac8d75c74bff1c2dc7df8543ddbc794cd2eb4619a71a7af08887c1bfc039f3aa51906a0c60c66fd6309ef74161c8d573c480d8b175d8705f769c2afea599de916379ff8bd5e80d189198d0c3a8e72dcfd56b5033f8f2368767693d8d123c7a3c + + \ No newline at end of file From 0595bee82c3c4ef765954827f662ae0e46ace0ad Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 12 Dec 2025 16:54:21 +0200 Subject: [PATCH 11/14] Modified mock cfg file, corrected test auth import --- app/test/testauthimport.cpp | 9 ++++++++- test/test_data/test_auth_config.xml | 4 +--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/test/testauthimport.cpp b/app/test/testauthimport.cpp index ed8de57fa..a11543980 100644 --- a/app/test/testauthimport.cpp +++ b/app/test/testauthimport.cpp @@ -28,6 +28,10 @@ void TestAuthImport::initTestCase() // check that qca is correctly linked and the authentication system is not disabled QVERIFY2( !QgsApplication::authManager()->isDisabled(), "Authentication system is DISABLED. Check QCA/OpenSSL dependency deployment." ); + + QgsAuthManager *authMngr = QgsApplication::authManager(); + QVERIFY( authMngr->setMasterPassword( QStringLiteral( "merginMaps" ), true ) ); + QVERIFY( authMngr->masterPasswordIsSet() ); } void TestAuthImport::cleanupTestCase() @@ -90,6 +94,8 @@ void TestAuthImport::test_successful_of_authFile() bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); QVERIFY2( ok, "Importing auth config XML failed when Master Password was set." ); + int count = authMngr->configIds().count(); + QCOMPARE( count, 1 ); } void TestAuthImport::test_import_fails_authFile_missing() @@ -164,5 +170,6 @@ void TestAuthImport::test_reload_after_configuration_update() QVERIFY2( ok, "Reloading/overwriting the existing auth config failed." ); // the number of configurations should remain 1 (it was overwritten/refreshed) - QCOMPARE( authMngr->configIds().count(), 1 ); + int count = authMngr->configIds().count(); + QCOMPARE( count, 1 ); } \ No newline at end of file diff --git a/test/test_data/test_auth_config.xml b/test/test_data/test_auth_config.xml index 046800f3d..5e7812430 100644 --- a/test/test_data/test_auth_config.xml +++ b/test/test_data/test_auth_config.xml @@ -1,4 +1,2 @@ -603f9a4b796e6b521f45155aa2ec6d8c0cb20f6b4290bd79ea60df7917fa1ac75b8bd929ff3ff48de46d6def199f86c0e3d1bd9e671b866e911ed0e978bc0fa1ddd3adcb68a6c871e146acf6b91a2d38b54e108c962861983e2d7097a2afe0c99ea979efcdb02e4f8d42a45a94ff38c45726614e7a136926f95a267531fe2ed43a4b7e2a0a758811c1bdff9a39152eebac8d75c74bff1c2dc7df8543ddbc794cd2eb4619a71a7af08887c1bfc039f3aa51906a0c60c66fd6309ef74161c8d573c480d8b175d8705f769c2afea599de916379ff8bd5e80d189198d0c3a8e72dcfd56b5033f8f2368767693d8d123c7a3c - - \ No newline at end of file +5d34108945bed01b77d3039f4f906593e6409c30b6d819f9d2d462cd7b3d50b399dd54157b9cc76afe51f374c831b34edac9c11046fb60cc887e66b993fac2b56a2c101ce1f255faadcf1dd0916179bb9df8a12083731acdbd28b2d11addb8dd18c2ed0dead8970c7935e4fa14fc2e8fa5f0654c755370fdfaa7c3999fc8d7bcdf161bc3001605cc621a00d8993dcaddd92d68a534692655c91bb33ebd2e519c30e39888941248e49b1f03bf12c815ec50ea9935855d39e60ca805f6ebb6d3da From 2281ccab5252659f4640005d2327acdebf0fdcc8 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Mon, 15 Dec 2025 15:31:58 +0200 Subject: [PATCH 12/14] Revert "Modified mock cfg file, corrected test auth import" This reverts commit 0595bee82c3c4ef765954827f662ae0e46ace0ad. --- app/test/testauthimport.cpp | 9 +-------- test/test_data/test_auth_config.xml | 4 +++- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/test/testauthimport.cpp b/app/test/testauthimport.cpp index a11543980..ed8de57fa 100644 --- a/app/test/testauthimport.cpp +++ b/app/test/testauthimport.cpp @@ -28,10 +28,6 @@ void TestAuthImport::initTestCase() // check that qca is correctly linked and the authentication system is not disabled QVERIFY2( !QgsApplication::authManager()->isDisabled(), "Authentication system is DISABLED. Check QCA/OpenSSL dependency deployment." ); - - QgsAuthManager *authMngr = QgsApplication::authManager(); - QVERIFY( authMngr->setMasterPassword( QStringLiteral( "merginMaps" ), true ) ); - QVERIFY( authMngr->masterPasswordIsSet() ); } void TestAuthImport::cleanupTestCase() @@ -94,8 +90,6 @@ void TestAuthImport::test_successful_of_authFile() bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); QVERIFY2( ok, "Importing auth config XML failed when Master Password was set." ); - int count = authMngr->configIds().count(); - QCOMPARE( count, 1 ); } void TestAuthImport::test_import_fails_authFile_missing() @@ -170,6 +164,5 @@ void TestAuthImport::test_reload_after_configuration_update() QVERIFY2( ok, "Reloading/overwriting the existing auth config failed." ); // the number of configurations should remain 1 (it was overwritten/refreshed) - int count = authMngr->configIds().count(); - QCOMPARE( count, 1 ); + QCOMPARE( authMngr->configIds().count(), 1 ); } \ No newline at end of file diff --git a/test/test_data/test_auth_config.xml b/test/test_data/test_auth_config.xml index 5e7812430..046800f3d 100644 --- a/test/test_data/test_auth_config.xml +++ b/test/test_data/test_auth_config.xml @@ -1,2 +1,4 @@ -5d34108945bed01b77d3039f4f906593e6409c30b6d819f9d2d462cd7b3d50b399dd54157b9cc76afe51f374c831b34edac9c11046fb60cc887e66b993fac2b56a2c101ce1f255faadcf1dd0916179bb9df8a12083731acdbd28b2d11addb8dd18c2ed0dead8970c7935e4fa14fc2e8fa5f0654c755370fdfaa7c3999fc8d7bcdf161bc3001605cc621a00d8993dcaddd92d68a534692655c91bb33ebd2e519c30e39888941248e49b1f03bf12c815ec50ea9935855d39e60ca805f6ebb6d3da +603f9a4b796e6b521f45155aa2ec6d8c0cb20f6b4290bd79ea60df7917fa1ac75b8bd929ff3ff48de46d6def199f86c0e3d1bd9e671b866e911ed0e978bc0fa1ddd3adcb68a6c871e146acf6b91a2d38b54e108c962861983e2d7097a2afe0c99ea979efcdb02e4f8d42a45a94ff38c45726614e7a136926f95a267531fe2ed43a4b7e2a0a758811c1bdff9a39152eebac8d75c74bff1c2dc7df8543ddbc794cd2eb4619a71a7af08887c1bfc039f3aa51906a0c60c66fd6309ef74161c8d573c480d8b175d8705f769c2afea599de916379ff8bd5e80d189198d0c3a8e72dcfd56b5033f8f2368767693d8d123c7a3c + + \ No newline at end of file From d1f4272a0a2fdbd755996cf7c762412d9579c5be Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Mon, 15 Dec 2025 15:34:03 +0200 Subject: [PATCH 13/14] Implemented code review findings --- app/CMakeLists.txt | 3 +- app/activeproject.cpp | 22 ++- app/main.cpp | 1 + app/test/inputtests.cpp | 6 - app/test/testactiveproject.cpp | 35 ++++ app/test/testactiveproject.h | 6 + app/test/testauthimport.cpp | 168 ------------------ app/test/testauthimport.h | 57 ------ test/CMakeLists.txt | 1 - .../project_auth_file/Survey_points.gpkg | Bin 0 -> 98304 bytes .../test_data/project_auth_file/auth-test.qgz | Bin 0 -> 19801 bytes test/test_data/project_auth_file/qgis_cfg.xml | 2 + test/test_data/test_auth_config.xml | 4 - 13 files changed, 60 insertions(+), 245 deletions(-) delete mode 100644 app/test/testauthimport.cpp delete mode 100644 app/test/testauthimport.h create mode 100644 test/test_data/project_auth_file/Survey_points.gpkg create mode 100644 test/test_data/project_auth_file/auth-test.qgz create mode 100644 test/test_data/project_auth_file/qgis_cfg.xml delete mode 100644 test/test_data/test_auth_config.xml diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 782a76383..63de0d899 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -204,7 +204,6 @@ if (ENABLE_TESTS) test/testlinks.cpp test/testmaptools.cpp test/testmerginapi.cpp - test/testauthimport.cpp test/testmodels.cpp test/testposition.cpp test/testrememberattributescontroller.cpp @@ -231,7 +230,6 @@ if (ENABLE_TESTS) test/testlinks.h test/testmaptools.h test/testmerginapi.h - test/testauthimport.cpp test/testmodels.h test/testposition.h test/testrememberattributescontroller.h @@ -508,6 +506,7 @@ if (LNX) target_link_libraries(MerginMaps PUBLIC QGIS::Core) endif () +# required for QGIS authentication manager API find_library( QCA_OSSL_PLUGIN_LIB NAMES qca-ossl diff --git a/app/activeproject.cpp b/app/activeproject.cpp index d3543a73e..06359aa0b 100644 --- a/app/activeproject.cpp +++ b/app/activeproject.cpp @@ -159,14 +159,22 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) emit projectWillBeReloaded(); mActiveLayer.resetActiveLayer(); - // import authentication database before loading the layers - QgsAuthManager *authMngr = QgsApplication::authManager(); - QString projectDir = mLocalProjectsManager.projectFromProjectFilePath( filePath ).projectDir ; + // path to the authenticationn configuration file + QFileInfo fileInfo( filePath ); + QString projectDir = fileInfo.absolutePath(); QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); - QString projectId = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir ) ).projectId; - - bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, projectId, true ); - CoreUtils::log( "AuthSync manager", QString( "QGIS auth imported: %1" ).arg( ok ? "true" : "false" ) ); + QFileInfo cfgFile( authFile ); + if ( cfgFile.exists() && cfgFile.isFile() ) + { + // clear the authentication database before importing a new one, if it exists + QgsAuthManager *authMngr = QgsApplication::authManager(); + authMngr->removeAllAuthenticationConfigs(); + + // import the new configuration, if it exists. + QString projectId = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir ) ).projectId; + bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, projectId, true ); + CoreUtils::log( "Database authentication: ", QString( "QGIS auth imported: %1" ).arg( ok ? "true" : "false" ) ); + } res = mQgsProject->read( filePath ); if ( !res ) diff --git a/app/main.cpp b/app/main.cpp index 889ff7a81..f1cae33e2 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -156,6 +156,7 @@ #include "qgsapplication.h" #include "activeproject.h" #include "appsettings.h" +// required for QGIS authentication manager API #include Q_IMPORT_PLUGIN( opensslPlugin ) diff --git a/app/test/inputtests.cpp b/app/test/inputtests.cpp index 92f7f56b8..282f9c99c 100644 --- a/app/test/inputtests.cpp +++ b/app/test/inputtests.cpp @@ -11,7 +11,6 @@ #include "testsketching.h" #include "testmerginapi.h" -#include "testauthimport.h" #include "testlinks.h" #include "testutilsfunctions.h" @@ -206,11 +205,6 @@ int InputTests::runTest() const TestMultiEditManager multiEditManagerTest; nFailed = QTest::qExec( &multiEditManagerTest, mTestArgs ); } - else if ( mTestRequested == "--testAuthImport" ) - { - TestAuthImport authImportTest; - nFailed = QTest::qExec( &authImportTest, mTestArgs ); - } else { qDebug() << "invalid test requested" << mTestRequested; diff --git a/app/test/testactiveproject.cpp b/app/test/testactiveproject.cpp index c3189f368..192504a3b 100644 --- a/app/test/testactiveproject.cpp +++ b/app/test/testactiveproject.cpp @@ -191,3 +191,38 @@ void TestActiveProject::testLoadingFlagFileExpiration() QVERIFY( !flagFile.exists() ); } + +void TestActiveProject::testLoadingAuthFileFromConfiguration() +{ + AppSettings as; + ActiveLayer al; + ActiveProject activeProject( as, al, mApi->localProjectsManager() ); + QString projectDir = TestUtils::testDataDir() + "/project_auth_file/"; + QString projectName = QStringLiteral( "auth-test.qgz" ); + QString af = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); + + QgsApplication::initQgis(); + + QgsAuthManager *am = QgsApplication::authManager(); + + mApi->localProjectsManager().addLocalProject( projectDir, projectName ); + activeProject.load( projectDir + projectName ); + + QSignalSpy spyLoadingStarted( &activeProject, &ActiveProject::loadingStarted ); + + // we expect the configuration import to fail as the password for the cfg xml is not the project's id + int count = am->configIds().count(); + QCOMPARE( count, 0 ); + + am->removeAllAuthenticationConfigs(); + QFileInfo cfgFile( af ); + if ( cfgFile.exists() && cfgFile.isFile() ) + { + // we still check that the configuration can be imported + bool ok = am->importAuthenticationConfigsFromXml( af, AUTH_CONFIG_PASSWORD, true ); + + QVERIFY2( ok, "Importing the authentication database from XML failed" ); + count = am->configIds().count(); + QCOMPARE( count, 1 ); + } +} diff --git a/app/test/testactiveproject.h b/app/test/testactiveproject.h index 428e81bef..c52eed1ca 100644 --- a/app/test/testactiveproject.h +++ b/app/test/testactiveproject.h @@ -12,6 +12,11 @@ #include #include +#include +#include + +const QString AUTH_CONFIG_FILENAME = "qgis_cfg.xml"; +const QString AUTH_CONFIG_PASSWORD = "1234"; class TestActiveProject : public QObject { @@ -29,6 +34,7 @@ class TestActiveProject : public QObject void testPositionTrackingFlag(); void testRecordingAllowed(); void testLoadingFlagFileExpiration(); + void testLoadingAuthFileFromConfiguration(); private: MerginApi *mApi; diff --git a/app/test/testauthimport.cpp b/app/test/testauthimport.cpp deleted file mode 100644 index ed8de57fa..000000000 --- a/app/test/testauthimport.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "testauthimport.h" -#include "testutils.h" -#include - -TestAuthImport::TestAuthImport() - : QObject( nullptr ) - , mPass( "1234" ) -{ -} - -void TestAuthImport::initTestCase() -{ - if ( !QgsApplication::instance() ) - { - static int argc = 1; - static char *argv[] = { ( char * )"test_app", nullptr }; - new QgsApplication( argc, argv, true, "TestApp" ); - } - - // create a temporary folder - mAppTempDir.reset( new QTemporaryDir() ); - QVERIFY( mAppTempDir->isValid() ); - qputenv( "QGIS_AUTH_DB_DIR_PATH", mAppTempDir->path().toLatin1() ); - - // init QGIS - QgsApplication::initQgis(); - - // check that qca is correctly linked and the authentication system is not disabled - QVERIFY2( !QgsApplication::authManager()->isDisabled(), - "Authentication system is DISABLED. Check QCA/OpenSSL dependency deployment." ); -} - -void TestAuthImport::cleanupTestCase() -{ - QgsApplication::authManager()->clearMasterPassword(); - QgsApplication::authManager()->removeAllAuthenticationConfigs(); - mAppTempDir.reset(); - QgsApplication::exitQgis(); -} - -void TestAuthImport::cleanup() -{ - // Clear configs after each test to ensure isolation - QgsApplication::authManager()->removeAllAuthenticationConfigs(); -} - -void TestAuthImport::mockProjectMetadata( const QString &projectDir ) -{ - // method to create a mock .mergin/metadata.json file with TEST_PROJECT_ID - QString metadataPath = CoreUtils::getProjectMetadataPath( projectDir ); - QDir().mkpath( QFileInfo( metadataPath ).path() ); - - QFile file( metadataPath ); - if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) ) - { - QTextStream stream( &file ); - stream << "{\"projectId\":\"" << TEST_PROJECT_ID << "\"}"; - file.close(); - QVERIFY( true ); - } -} - -void TestAuthImport::copyTestAuthFile( const QString &projectDir ) -{ - const QString sourcePath = TestUtils::testDataDir() + "/" + ( TEST_XML_FILENAME ); - const QString destPath = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); - - QVERIFY2( QFile::exists( sourcePath ), - qPrintable( QString( "Source XML file missing: %1" ).arg( sourcePath ) ) ); - - // copy the pre-built, encrypted cfg XML file into the temporary project directory - QVERIFY2( QFile::copy( sourcePath, destPath ), - qPrintable( QString( "Failed to copy test XML from %1 to %2" ).arg( sourcePath, destPath ) ) ); -} - -void TestAuthImport::test_successful_of_authFile() -{ - // authentication DB config file is present. Should succeed. - - // setup project dir, mock metadata, and copy the encrypted XML file - QTemporaryDir projectTempDir; - const QString projectDir = projectTempDir.path(); - mockProjectMetadata( projectDir ); - copyTestAuthFile( projectDir ); - - // initialise the QGIS authentication manager - QgsAuthManager *authMngr = QgsApplication::authManager(); - QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); - - bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); - - QVERIFY2( ok, "Importing auth config XML failed when Master Password was set." ); -} - -void TestAuthImport::test_import_fails_authFile_missing() -{ - // authentication DB config fileis missing, negative test case. - - // setup project dir and mock metadata (NO call to copyTestAuthFile) - QTemporaryDir projectTempDir; - const QString projectDir = projectTempDir.path(); - mockProjectMetadata( projectDir ); - - // initialise the QGIS authentication manager - QgsAuthManager *authMngr = QgsApplication::authManager(); - QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); - - bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); - - // import must fail as the auth manager fails to open the file - QVERIFY2( !ok, "Importing should have failed because the auth file was missing." ); - - // no configs should have been loaded - QCOMPARE( authMngr->configIds().count(), 0 ); -} - -void TestAuthImport::test_import_fails_master_password_not_set() -{ - // authentication DB config file is present, master Password is NOT set, import must fail - - // setup project dir, mock metadata, and copy the encrypted XML file - QTemporaryDir projectTempDir; - const QString projectDir = projectTempDir.path(); - mockProjectMetadata( projectDir ); - copyTestAuthFile( projectDir ); - - // make sure that the master Password is NOT set - QgsAuthManager *authMngr = QgsApplication::authManager(); - authMngr->clearMasterPassword(); - QVERIFY( !authMngr->masterPasswordIsSet() ); - - QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); - bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); - - // the import method does not return false if no master password is set - // however it does not load any configurations, thus the import failed - QVERIFY2( ok, "Importing should have failed because the Master Password was not set." ); - QCOMPARE( authMngr->configIds().count(), 0 ); - - // we set master password again so that the following test does not fail - QVERIFY( authMngr->setMasterPassword( QStringLiteral( "merginMaps" ), true ) ); - QVERIFY( authMngr->masterPasswordIsSet() ); -} - -void TestAuthImport::test_reload_after_configuration_update() -{ - // import works, then reload works, as overwrite=true flag is effective, when the cfg xml file is updated - - // setup project dir, mock metadata, copy XML, and set Master Password - QTemporaryDir projectTempDir; - const QString projectDir = projectTempDir.path(); - mockProjectMetadata( projectDir ); - copyTestAuthFile( projectDir ); - - QgsAuthManager *authMngr = QgsApplication::authManager(); - QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); - - QVERIFY( authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ) ); - QCOMPARE( authMngr->configIds().count(), 1 ); - - // run the import call again, which happends after the configuration file has been resynchronised - bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, mPass, true ); - - QVERIFY2( ok, "Reloading/overwriting the existing auth config failed." ); - - // the number of configurations should remain 1 (it was overwritten/refreshed) - QCOMPARE( authMngr->configIds().count(), 1 ); -} \ No newline at end of file diff --git a/app/test/testauthimport.h b/app/test/testauthimport.h deleted file mode 100644 index 4e4e7768d..000000000 --- a/app/test/testauthimport.h +++ /dev/null @@ -1,57 +0,0 @@ -/*************************************************************************** - * * - * 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 TESTAUTHIMPORT_H -#define TESTAUTHIMPORT_H - -#include -#include -#include -#include -#include - -#include -#include - -#include "coreutils.h" -#include "merginprojectmetadata.h" -#include "localprojectsmanager.h" - -const QString AUTH_CONFIG_FILENAME = "qgis_auth.xml"; -const QString TEST_PROJECT_ID = "mobile_test_auth_1234"; -const QString TEST_AUTH_ID = "mobile_test_config_id"; -const QString TEST_XML_FILENAME = "test_auth_config.xml"; - -class TestAuthImport : public QObject -{ - Q_OBJECT - - public: - TestAuthImport(); - - private slots: - void initTestCase(); - void cleanupTestCase(); - void cleanup(); - - void test_successful_of_authFile(); - void test_import_fails_authFile_missing(); - void test_import_fails_master_password_not_set(); - void test_reload_after_configuration_update(); - - private: - std::unique_ptr mAppTempDir; - const char *mPass = nullptr; - - void mockProjectMetadata( const QString &projectDir ); - void setup_ExportInitialConfig( const QString &projectDir, const QString &authId, const QString &username ); - void copyTestAuthFile( const QString &projectDir ); -}; - -#endif // TESTAUTHIMPORT_H \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 70f906675..8e6cb6461 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,7 +10,6 @@ set(MM_TESTS testLinks testUtils testAttributePreviewController - testAuthImport testMerginApi testAttributeController testIdentifyKit diff --git a/test/test_data/project_auth_file/Survey_points.gpkg b/test/test_data/project_auth_file/Survey_points.gpkg new file mode 100644 index 0000000000000000000000000000000000000000..823bf9ced67e75e28b236f608bf0179cf59c81ad GIT binary patch literal 98304 zcmeI5-ESLLcECy55+&=)-o#;ACGicj+99ivB}!Ig*;W%;8q$31 z`Y`gHkxPR+k=3(T2fhsd&|MHecL*G;pN~wHW{00%&{e5cqiU`BuqNnIoeE$yRoZpl zcBG2FEl9FPRoySMR^U=RNBBY}o#qOJ@#lA|gCRG$TloSX9;8JMHHFn;)6OROh5;QA&(`cF6vYxWkuF> zRg`4?8nIhxHjk7=SypuNfRZ}ZG@@^dGD$KCa_=?=n#Ob7y~`f?8zh;yPPp9q!AFyk zsk?yj-Ch`1X5Cb)|5tcUB4gN}%bYxBl)Y9=dKwT5{%d=53faCM=kPeZV}XM|RF@`*a)TOZLZ(VqLwl4}6DfW0C0WZ20pPvoHAl%zr%Y zI9}>zmLvXL5;rRmWU)aB&wav^TpoT)*(~eQG?U{wqvyBtJiVk>?lzTb+d2dQUtu%) zSRJx^Uax{pX&1WJuAoUzC|S>!j0Uw;vNHF&XRhUQMIJ&*jwj8Bg045nW+AhYDr}KE z+?JaG>*`_&)>W(u#TpI4Mazh8nRL!yLxL2zTM$liYh2O74dFR>d_kjuIBukk9_bE-gd2y~ zCEU=Gyi2&RuI=}xvT$-= z%8U#}KU+T1m)b_LJwjOM>J#TO!!KTy6@C0?7}_Jj4tcktyC#%4PA-U&``8AcO)+NdF_7ZPKq49H4uq1 zO!(POr){2gIR|HgZE~{Qj&?+YV74d-2IpXPe>LwW1wEDklNsQu5dqAhBdAV+h+}`N)<_z z;q;z-tt`T|s;XE9^f5N+$*YQWjS3Xjypmk=7ql{EMb)>RT&tPJz8SPK*@ef-yu~B+ zNd|1oITdrMA`i69#9X_=99){Z{TT~;=3K65w$gA4Fl=n5sVd)Ig%*{`jJRrBmCQWo ze1z*%g08L3lbO0A3-iW@UF&VndV9E?&r5&vh*vF} z7Bbh1k_^|h2yAjvmC9vGVEw4m#{J(ougLY##qgzvowJlh6|TjBMV*uUrd@ga16%@e zgpN4L;p=}{9hre%`Bd6}+wO4Ch6>fCve6^X|NH-!zGix078dw=s{$@;d!Xf*Sw*hd z>9)TH;)dNb!R0K;IdDU>2<)K3mVjwujYu-7(6ZDJ>+o(w1kJomxJ`-8UTbbsRgo&j z`aeARqY(Vy3ke_rB!C2v01`j~NB{{S0VIF~kN^^RwFsocqs-gP_^P4RK(VDhrh7sY zzIUi=_BU%ZPvXM-!qRMVVK%wMCzIEf-oJKb;WF$^emZ&K98?gV{I3xF;0p;L0VIF~ zkN^@u0!RP}AOR$R1dsp{_<9gHe})DQBz5k4D0KEU-i%~>rB!C2v01`j~NB{{S0VIF~ zkN^@u0;hmL*YE#_&YuF!QAH$x1dsp{Kmter2_OL^fCP{L5;Jg^KQ=xr zhy;)T5;F?>DpV2)AOR$R1dsp{Kmter2_OL^ zfCP}hu?gVy|6`-Wf=B=fAOR$R1dsp{Kmter2_OL^fCNqrf$@o7g(e5S3{Cv^*uPHv zXyOmY!eieb*&h93^kL*XBbNsMZgA)9)qyX=KMZa3(WHrA4g6rLG&}q>sjE`0M%7yL zVNKAbIu#lsB<(tHJ5oj87Fx}Us8hepT7gUP9N`O@beby=#-HD<4u;(1ZsiLcDQ&JB zh4Z(FeM0k0B%ce`!w@JzxYXKhQpn$f3euSzDRNnEjVFavrpU!pH}eIaC5)vfQDmZv z59*XK0i76zJeHI?v_>OkMb>mxlw|#ySBbJH%Zg4OP*SIwM)Yk_CP^ki?%n1<^LUQC zciE%#4U$Y;CtPm*VEuf=#CoAGtdguzRrh0iF|1Y{ZLFDGkt^^g#Awy|5;1->8JW5Z z-uZ4Xj87D$A=K$(T0gFD25M*{v^`U)l8(#?fgay4T`F|*%6YF>x}rgZBHX{TQHFx z9g5yx>bsu;`Cwl)>@oH<6p2Kam&4z^)akFr{g18; zt(v=a>~l(o%m!+Yv87DJZskyz+tu_nOCWC>gh^Nc7 zpmf>oC8b>EZVBc~olfYHhc0T!%U;8~g!}55es3xZC-D`)>?JqH}ZM{rerqRB9?%%ZgW~0`7#NphLQ|U3NJl@KmX&w^Rye zX~~_zVV%2`DrK#aCy^Q&ik9Y%#N|G(-S6B!PL$Wmp~5V0?qs z44>KPjBvyejKdk(?#&2+lTK zfg7@10oyk&1UrLjbO&SPI8JnJ2iKLeL(vW9NDDhwusectIJk1A|Du-TJ4MgJI(FDt z%O0Ewn`7@`qT?LCalrI_U)n&>ReTcG=+R{^)gz+1VuETeQRwY?7tMIv_-|rIR=YAX-86csFzaM`vvOhdM^s~V~I-4B$=K(UVhW;b` zZ=t(mKOOsU^e_5Y{aYgEejNU%=<4e5{&f?ps;iU=o~I9*qd~89o9@S%0$)mH32)9< zW4)wfq*To0(!|V3}*&28|hpz=q z+{p~YgTNTewuQS5t1jDkXWCj>2}BRqC|aX}UZx~C`gw*8z$F#El%C*gyXLDyVT)d z?ZZBlTk55)_dQB9TAH~FRa0RQhH=T*MS#DkI%TKMv?#cYrQ%6rfwU^Z|YroWDYb)_$Z0$lWKYhI~Kef4>$WJ|W zwAsGoP1CQE<<@JFse7<^j_OhO6^p1I-A;fq6dL%muhr$Jee!N3dgo4ffBDF{w9ph)SY`H_ zB-`0h?*#2R(y_R#z|vLeHe+M0^BDq({ntq(nofuJnWI%SgnORS$b6$u^$gh~l`~f2 ziTyV(MWUHZcz@<-wNwSRpnK|N=3eYmF;h4eH{aaU4)_b!K!g`X@uFzp+OieSU*1Jg z{Qv*HcQ#NE2_OL^fCP{L5;F@0I#e79AOR$R1dsp{Kmter z2_OL^fCP|0-vo^He|YlGL-2zyB!C2v01`j~NB{{S0VIF~kN^@u0!ZMuPoNwgVlK`; zde`%>)W-iro3|yf{%OU$U(~j)H?Pgj?d3yoYth}xW`&MLXgBd<-J+HB?COi+8Uh$~ccWdp!PIH|{Q1@tVTSfBjD#>y8EPY8T zw@%{bp@kw-G(qGGq{s^yjcYXZJy(7raZE;lHQrW%?I@5ii5-40Z4&$ZVt>RIPeN?* zt0e}`V^UU@FDR7(F)EZ|z$v1zLkA9rWmbjvAVZ%XLP;Wz$KXKcX zHFVw(cfE<0;8r(5PoibPgWvn6EV$se;4NbJ`EsN}p#9>v71(Glo^xv4w-9N3f2|bSE%4 z^%r2AnqR>ffe(ytsgLQN&{QN@*92q06;9m)<2O=;Epmt3Vhzn@C9(h7JCW$>>hS*c zBcEm8YtClRc0SG&_);oM_|(lTchrDZDrRzNV%8RyDlBP8@-F-pca6W2yk~yiORT;< z6umnW4w-i#(;n5~hGK%)(gA#h?h58D%Gx&_(}bii;r0EUw<6J*negGX*(hJO@9;He z#%Aj7%Or8L20<1Zl+hq?F6tEpcuoSmIKECEk-K5(iULktq@8t}AvhwBH14UM^IrsJB!q zl$DmO3!YHuIdGUbbN2qLaYAJ8>AN*cx97ViZ>6S!MzT(!Uy&lm6OWoO=6jE4NzzyY z!0f%Q>`>KZq6ElVX^f{ij6KVl`rYnytmuAXI?u1hq2q-4-~e)BIy1+mv-vwiMyJ^_ fJ>Kqg8hzBFa+&%Lzv1|Y*=Ky2aig=aP%`nqU8cO` literal 0 HcmV?d00001 diff --git a/test/test_data/project_auth_file/auth-test.qgz b/test/test_data/project_auth_file/auth-test.qgz new file mode 100644 index 0000000000000000000000000000000000000000..25c66243e0e36cf3bcf09b8dcaf29f6f5bf79b6e GIT binary patch literal 19801 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Ν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 literal 0 HcmV?d00001 diff --git a/test/test_data/project_auth_file/qgis_cfg.xml b/test/test_data/project_auth_file/qgis_cfg.xml new file mode 100644 index 000000000..3cbac7b8a --- /dev/null +++ b/test/test_data/project_auth_file/qgis_cfg.xml @@ -0,0 +1,2 @@ + +21ce1cf53ab351eb229b3ae83f7384b76578dd93d76e3515904213a79dddd46b0ed3e05f5f906fa681e7714af0d0d9dd551735a31bae5ea96333cc9ebe2debc003eebf44e42c1ec5cd9c7214d8a7399dedfd2f89819661615013ec79fb3bce642138f593be11c64f4f1374b274e1b5c887f229190521e7d0a340f97f1a27c78d70f1e3ac28d9b605d0ae07a9832c03ea2778c0629cbbc8e932d5b36b941f6d4eabe0b4d734d10ce41464064da7e2ee14204064a1ddfa7ca9427ad3aa4fd0cc467bab98b12235a867ddc6542935db835b diff --git a/test/test_data/test_auth_config.xml b/test/test_data/test_auth_config.xml deleted file mode 100644 index 046800f3d..000000000 --- a/test/test_data/test_auth_config.xml +++ /dev/null @@ -1,4 +0,0 @@ - -603f9a4b796e6b521f45155aa2ec6d8c0cb20f6b4290bd79ea60df7917fa1ac75b8bd929ff3ff48de46d6def199f86c0e3d1bd9e671b866e911ed0e978bc0fa1ddd3adcb68a6c871e146acf6b91a2d38b54e108c962861983e2d7097a2afe0c99ea979efcdb02e4f8d42a45a94ff38c45726614e7a136926f95a267531fe2ed43a4b7e2a0a758811c1bdff9a39152eebac8d75c74bff1c2dc7df8543ddbc794cd2eb4619a71a7af08887c1bfc039f3aa51906a0c60c66fd6309ef74161c8d573c480d8b175d8705f769c2afea599de916379ff8bd5e80d189198d0c3a8e72dcfd56b5033f8f2368767693d8d123c7a3c - - \ No newline at end of file From 9f60b2f5ad7a783048d43ceec31fefd509de4a18 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Thu, 18 Dec 2025 18:00:21 +0200 Subject: [PATCH 14/14] Implemented code findings --- app/activeproject.cpp | 14 ++++++------- app/test/testactiveproject.cpp | 38 +++++++++++++++++----------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/app/activeproject.cpp b/app/activeproject.cpp index 06359aa0b..4a472e7dd 100644 --- a/app/activeproject.cpp +++ b/app/activeproject.cpp @@ -27,7 +27,7 @@ #include "position/tracking/androidtrackingbroadcast.h" #endif -const QString AUTH_CONFIG_FILENAME = "qgis_cfg.xml"; +const QString AUTH_CONFIG_FILENAME = QStringLiteral( "qgis_cfg.xml" ); const QString ActiveProject::LOADING_FLAG_FILE_PATH = QString( "%1/.input_loading_project" ).arg( QStandardPaths::standardLocations( QStandardPaths::TempLocation ).first() ); const int ActiveProject::LOADING_FLAG_FILE_EXPIRATION_MS = 5000; @@ -159,11 +159,9 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) emit projectWillBeReloaded(); mActiveLayer.resetActiveLayer(); - // path to the authenticationn configuration file - QFileInfo fileInfo( filePath ); - QString projectDir = fileInfo.absolutePath(); - QString authFile = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); - QFileInfo cfgFile( authFile ); + // path to the authentication configuration file + const QDir projectDir = QFileInfo( filePath ).dir(); + const QFileInfo cfgFile( projectDir.filePath( AUTH_CONFIG_FILENAME ) ); if ( cfgFile.exists() && cfgFile.isFile() ) { // clear the authentication database before importing a new one, if it exists @@ -171,8 +169,8 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) authMngr->removeAllAuthenticationConfigs(); // import the new configuration, if it exists. - QString projectId = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir ) ).projectId; - bool ok = authMngr->importAuthenticationConfigsFromXml( authFile, projectId, true ); + const QString projectId = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir.path() ) ).projectId; + const bool ok = authMngr->importAuthenticationConfigsFromXml( cfgFile.path(), projectId, true ); CoreUtils::log( "Database authentication: ", QString( "QGIS auth imported: %1" ).arg( ok ? "true" : "false" ) ); } diff --git a/app/test/testactiveproject.cpp b/app/test/testactiveproject.cpp index 192504a3b..834f11e3f 100644 --- a/app/test/testactiveproject.cpp +++ b/app/test/testactiveproject.cpp @@ -36,8 +36,8 @@ void TestActiveProject::testProjectValidations() QString projectFilename = "bad_layer.qgz"; AppSettings as; - ActiveLayer al; - ActiveProject activeProject( as, al, mApi->localProjectsManager() ); + ActiveLayer activeLayer; + ActiveProject activeProject( as, activeLayer, mApi->localProjectsManager() ); QSignalSpy spyReportIssues( &activeProject, &ActiveProject::reportIssue ); QSignalSpy spyErrorsFound( &activeProject, &ActiveProject::loadingErrorFound ); @@ -61,8 +61,8 @@ void TestActiveProject::testProjectLoadFailure() InputUtils::cpDir( TestUtils::testDataDir() + "/load_failure", projectdir ); AppSettings as; - ActiveLayer al; - ActiveProject activeProject( as, al, mApi->localProjectsManager() ); + ActiveLayer activeLayer; + ActiveProject activeProject( as, activeLayer, mApi->localProjectsManager() ); mApi->localProjectsManager().addLocalProject( projectdir, projectname ); @@ -81,8 +81,8 @@ void TestActiveProject::testPositionTrackingFlag() // the position tracking availability is correctly set AppSettings as; - ActiveLayer al; - ActiveProject activeProject( as, al, mApi->localProjectsManager() ); + ActiveLayer activeLayer; + ActiveProject activeProject( as, activeLayer, mApi->localProjectsManager() ); // project "planes" - tracking not enabled QString projectDir = TestUtils::testDataDir() + "/planes/"; @@ -121,8 +121,8 @@ void TestActiveProject::testRecordingAllowed() QString projectFilename = "tracking-project.qgz"; AppSettings as; - ActiveLayer al; - ActiveProject activeProject( as, al, mApi->localProjectsManager() ); + ActiveLayer activeLayer; + ActiveProject activeProject( as, activeLayer, mApi->localProjectsManager() ); mApi->localProjectsManager().addLocalProject( projectDir, projectFilename ); QVERIFY( activeProject.load( projectDir + "/" + projectFilename ) ); @@ -169,8 +169,8 @@ void TestActiveProject::testRecordingAllowed() void TestActiveProject::testLoadingFlagFileExpiration() { AppSettings as; - ActiveLayer al; - ActiveProject activeProject( as, al, mApi->localProjectsManager() ); + ActiveLayer activeLayer; + ActiveProject activeProject( as, activeLayer, mApi->localProjectsManager() ); // project "planes" - tracking not enabled QString projectDir = TestUtils::testDataDir() + "/planes/"; @@ -194,16 +194,16 @@ void TestActiveProject::testLoadingFlagFileExpiration() void TestActiveProject::testLoadingAuthFileFromConfiguration() { - AppSettings as; - ActiveLayer al; - ActiveProject activeProject( as, al, mApi->localProjectsManager() ); - QString projectDir = TestUtils::testDataDir() + "/project_auth_file/"; + AppSettings appSettings; + ActiveLayer activeLayer; + ActiveProject activeProject( appSettings, activeLayer, mApi->localProjectsManager() ); + QString projectDir = TestUtils::testDataDir() + QStringLiteral( "/project_auth_file/" ); QString projectName = QStringLiteral( "auth-test.qgz" ); QString af = QDir( projectDir ).filePath( AUTH_CONFIG_FILENAME ); QgsApplication::initQgis(); - QgsAuthManager *am = QgsApplication::authManager(); + QgsAuthManager *authManager = QgsApplication::authManager(); mApi->localProjectsManager().addLocalProject( projectDir, projectName ); activeProject.load( projectDir + projectName ); @@ -211,18 +211,18 @@ void TestActiveProject::testLoadingAuthFileFromConfiguration() QSignalSpy spyLoadingStarted( &activeProject, &ActiveProject::loadingStarted ); // we expect the configuration import to fail as the password for the cfg xml is not the project's id - int count = am->configIds().count(); + int count = authManager->configIds().count(); QCOMPARE( count, 0 ); - am->removeAllAuthenticationConfigs(); + authManager->removeAllAuthenticationConfigs(); QFileInfo cfgFile( af ); if ( cfgFile.exists() && cfgFile.isFile() ) { // we still check that the configuration can be imported - bool ok = am->importAuthenticationConfigsFromXml( af, AUTH_CONFIG_PASSWORD, true ); + bool ok = authManager->importAuthenticationConfigsFromXml( af, AUTH_CONFIG_PASSWORD, true ); QVERIFY2( ok, "Importing the authentication database from XML failed" ); - count = am->configIds().count(); + count = authManager->configIds().count(); QCOMPARE( count, 1 ); } }