diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index e49bfd223..63de0d899 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -506,10 +506,24 @@ 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 + 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 () + target_link_libraries( MerginMaps PUBLIC Qt6Keychain::Qt6Keychain qca + ${QCA_OSSL_PLUGIN_LIB} GDAL::GDAL PostgreSQL::PostgreSQL Spatialite::Spatialite diff --git a/app/activeproject.cpp b/app/activeproject.cpp index 8a91f5c52..1eae34b5f 100644 --- a/app/activeproject.cpp +++ b/app/activeproject.cpp @@ -17,6 +17,8 @@ #include "qgslayertreelayer.h" #include "qgslayertreegroup.h" #include "qgsmapthemecollection.h" +#include "qgsauthmanager.h" +#include "qgsapplication.h" #include "activeproject.h" #include "coreutils.h" @@ -25,6 +27,8 @@ #include "position/tracking/androidtrackingbroadcast.h" #endif +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; @@ -155,6 +159,21 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) emit projectWillBeReloaded(); mActiveLayer.resetActiveLayer(); + // 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 + QgsAuthManager *authMngr = QgsApplication::authManager(); + authMngr->removeAllAuthenticationConfigs(); + + // import the new configuration, if it exists. + 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" ) ); + } + res = mQgsProject->read( filePath ); if ( !res ) { @@ -186,6 +205,7 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) QString role = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( mLocalProject.projectDir ) ).role; setProjectRole( role ); + updateMapTheme(); updateActiveLayer(); updateMapSettingsLayers(); diff --git a/app/main.cpp b/app/main.cpp index e5bda4ca6..ab8ce472e 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" @@ -155,6 +156,9 @@ #include "qgsapplication.h" #include "activeproject.h" #include "appsettings.h" +// required for QGIS authentication manager API +#include +Q_IMPORT_PLUGIN( opensslPlugin ) static QString getDataDir() { @@ -543,6 +547,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/app/test/testactiveproject.cpp b/app/test/testactiveproject.cpp index c3189f368..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/"; @@ -191,3 +191,38 @@ void TestActiveProject::testLoadingFlagFileExpiration() QVERIFY( !flagFile.exists() ); } + +void TestActiveProject::testLoadingAuthFileFromConfiguration() +{ + 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 *authManager = 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 = authManager->configIds().count(); + QCOMPARE( count, 0 ); + + authManager->removeAllAuthenticationConfigs(); + QFileInfo cfgFile( af ); + if ( cfgFile.exists() && cfgFile.isFile() ) + { + // we still check that the configuration can be imported + bool ok = authManager->importAuthenticationConfigsFromXml( af, AUTH_CONFIG_PASSWORD, true ); + + QVERIFY2( ok, "Importing the authentication database from XML failed" ); + count = authManager->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/test/test_data/project_auth_file/Survey_points.gpkg b/test/test_data/project_auth_file/Survey_points.gpkg new file mode 100644 index 000000000..823bf9ced Binary files /dev/null and b/test/test_data/project_auth_file/Survey_points.gpkg differ 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 000000000..25c66243e Binary files /dev/null and b/test/test_data/project_auth_file/auth-test.qgz differ 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