Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5bc1e59
Added initial draft for the auth db sync
gabriel-bolbotina Dec 8, 2025
50354d6
Modified authsync
gabriel-bolbotina Dec 9, 2025
c251edd
Formatted code
gabriel-bolbotina Dec 9, 2025
0caf555
Simplified class
gabriel-bolbotina Dec 9, 2025
5d5f9ad
Formatted code
gabriel-bolbotina Dec 9, 2025
9d1cc77
Qca-workaround
gabriel-bolbotina Dec 10, 2025
651b714
Modified auth db sync
gabriel-bolbotina Dec 12, 2025
34e5521
Formatted cmake file
gabriel-bolbotina Dec 12, 2025
e0ca6f2
Implemented code review findings
gabriel-bolbotina Dec 12, 2025
6c424a1
Removed authSync class + header
gabriel-bolbotina Dec 12, 2025
f05ee2f
Modified mock cfg file, corrected test auth import
gabriel-bolbotina Dec 12, 2025
bdf48b4
Revert "Modified mock cfg file, corrected test auth import"
gabriel-bolbotina Dec 15, 2025
7eb22b2
Implemented code review findings
gabriel-bolbotina Dec 15, 2025
05ebb9b
Implemented code findings
gabriel-bolbotina Dec 18, 2025
83470ec
Updated unit test
gabriel-bolbotina Jan 21, 2026
962ad45
Modified cmake for windows qca
gabriel-bolbotina Jan 21, 2026
1fdd8f3
Used windows dll
gabriel-bolbotina Jan 21, 2026
e1a2031
Fix QCA for windows build
Withalion Jan 22, 2026
5608f8f
Excluded opensslPlugin from the windows build
gabriel-bolbotina Jan 22, 2026
fe5119e
Modified active project
gabriel-bolbotina Jan 23, 2026
f0b9dec
Updated comments
gabriel-bolbotina Jan 30, 2026
f848d4a
Implemented code findings
gabriel-bolbotina Feb 2, 2026
d71664f
Fixed authentication database logic
gabriel-bolbotina Feb 4, 2026
b3ceb62
Modified install step for qca
gabriel-bolbotina Feb 5, 2026
94c3197
Added function to clear all configurations
gabriel-bolbotina Feb 6, 2026
c5b001a
Added number of auth configurations to the logger
gabriel-bolbotina Feb 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,9 @@ endif ()
if (WIN)
add_compile_definitions(_HAS_AUTO_PTR_ETC=1)
add_compile_definitions(_USE_MATH_DEFINES)
# On windows the compiler instead of using standard min & max functions expands them to
# min & max macros in every file that includes windows header file
add_compile_definitions(NOMINMAX)
endif ()

if (IOS OR ANDROID)
Expand Down
23 changes: 23 additions & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ set(MM_HDRS
mmstyle.h
)

if (NOT WIN32)
set(MM_SRCS ${MM_SRCS} static_plugins.cpp)
endif ()

if (HAVE_BLUETOOTH)
set(MM_SRCS ${MM_SRCS} position/providers/bluetoothpositionprovider.cpp)

Expand Down Expand Up @@ -506,6 +510,21 @@ if (LNX)
target_link_libraries(MerginMaps PUBLIC QGIS::Core)
endif ()

if (NOT WIN32)
# for every other platform except windows
find_library(
QCA_OSSL_PLUGIN_LIB
NAMES qca-ossl libqca-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 ()
endif ()

target_link_libraries(
MerginMaps
PUBLIC Qt6Keychain::Qt6Keychain
Expand All @@ -515,6 +534,10 @@ target_link_libraries(
Spatialite::Spatialite
)

if (NOT WIN32)
target_link_libraries(MerginMaps PUBLIC ${QCA_OSSL_PLUGIN_LIB})
endif ()

target_link_libraries(
MerginMaps
PUBLIC Spatialindex::Spatialindex
Expand Down
25 changes: 25 additions & 0 deletions app/activeproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "qgslayertreelayer.h"
#include "qgslayertreegroup.h"
#include "qgsmapthemecollection.h"
#include "qgsapplication.h"

#include "activeproject.h"
#include "coreutils.h"
Expand All @@ -36,6 +37,7 @@ ActiveProject::ActiveProject( AppSettings &appSettings
QObject( parent )
, mAppSettings( appSettings )
, mActiveLayer( activeLayer )
, mAuthManager( QgsApplication::authManager() )
, mLocalProjectsManager( localProjectsManager )
, mProjectLoadingLog( "" )
{
Expand Down Expand Up @@ -110,6 +112,9 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force )
AndroidTrackingBroadcast::unregisterBroadcast();
#endif

// clear the authentication configurations from other projects before reloading
clearAllAuthenticationConfigs();

// Just clear project if empty
if ( filePath.isEmpty() )
{
Expand Down Expand Up @@ -155,6 +160,17 @@ 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( CoreUtils::AUTH_CONFIG_FILENAME ) );
if ( cfgFile.exists() && cfgFile.isFile() )
{
// import the new configuration, if it exists.
const QString projectId = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( projectDir.path() ) ).projectId;
const bool ok = mAuthManager->importAuthenticationConfigsFromXml( cfgFile.filePath(), projectId, true );
CoreUtils::log( "Authentication database", QStringLiteral( "QGIS auth import of %1 configuration(s) : %2" ).arg( mAuthManager->configIds().count() ).arg( ok ? "successful" : "failed" ) );
}

res = mQgsProject->read( filePath );
if ( !res )
{
Expand Down Expand Up @@ -186,6 +202,7 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force )
QString role = MerginProjectMetadata::fromCachedJson( CoreUtils::getProjectMetadataPath( mLocalProject.projectDir ) ).role;
setProjectRole( role );


updateMapTheme();
updateActiveLayer();
updateMapSettingsLayers();
Expand Down Expand Up @@ -495,6 +512,14 @@ void ActiveProject::updateActiveLayer()
}
}

void ActiveProject::clearAllAuthenticationConfigs()
{
for ( const auto configuration : mAuthManager->configIds() )
{
mAuthManager->removeAuthenticationConfig( configuration );
}
}

bool ActiveProject::isProjectLoaded() const
{
return mQgsProject && !mQgsProject->fileName().isEmpty();
Expand Down
5 changes: 5 additions & 0 deletions app/activeproject.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "appsettings.h"
#include "activelayer.h"
#include "qgsauthmanager.h"
#include "recordinglayersproxymodel.h"
#include "localprojectsmanager.h"
#include "autosynccontroller.h"
Expand Down Expand Up @@ -212,11 +213,15 @@ class ActiveProject: public QObject
//! Reloads layers in 'recording layers model'
void updateRecordingLayers();

// Clear all the authentication configurations from the database
void clearAllAuthenticationConfigs();

QgsProject *mQgsProject = nullptr;
LocalProject mLocalProject;

AppSettings &mAppSettings;
ActiveLayer &mActiveLayer;
QgsAuthManager *mAuthManager = nullptr;
LocalProjectsManager &mLocalProjectsManager;
InputMapSettings *mMapSettings = nullptr;
std::unique_ptr<AutosyncController> mAutosyncController;
Expand Down
9 changes: 9 additions & 0 deletions app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "test/inputtests.h"
#endif
#include <qqml.h>
#include "qgsauthmanager.h"
#include <qgsmessagelog.h>
#include "qgsconfig.h"
#include "qgsproviderregistry.h"
Expand Down Expand Up @@ -543,6 +544,14 @@ int main( int argc, char *argv[] )
LayerTreeFlatModelPixmapProvider *layerTreeFlatModelPixmapProvider( new LayerTreeFlatModelPixmapProvider );
LayerDetailLegendImageProvider *layerDetailLegendImageProvider( new LayerDetailLegendImageProvider );

QgsAuthManager *authManager = QgsApplication::authManager();
// remove existing authentication database when opening the app
authManager->removeAllAuthenticationConfigs();
// set up the master password for authentication database retrieval
authManager->setPasswordHelperEnabled( false );
authManager->setMasterPassword( QStringLiteral( "merginMaps" ), true );


// build position kit, save active provider to QSettings and load previously active provider
PositionKit pk;
QObject::connect( &pk, &PositionKit::positionProviderChanged, as, [as]( AbstractPositionProvider * provider )
Expand Down
3 changes: 3 additions & 0 deletions app/static_plugins.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <QtPlugin>
// Required for QGIS authentication manager API on static builds (Linux/macOS/iOS/android)
Q_IMPORT_PLUGIN( opensslPlugin )
53 changes: 43 additions & 10 deletions app/test/testactiveproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "testactiveproject.h"
#include "testutils.h"
#include "inpututils.h"
#include "coreutils.h"
#include "activeproject.h"

#include <QtTest/QtTest>
Expand All @@ -36,8 +37,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 );
Expand All @@ -61,8 +62,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 );

Expand All @@ -81,8 +82,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/";
Expand Down Expand Up @@ -121,8 +122,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 ) );
Expand Down Expand Up @@ -169,8 +170,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/";
Expand All @@ -191,3 +192,35 @@ 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 authFile = QDir( projectDir ).filePath( CoreUtils::AUTH_CONFIG_FILENAME );

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
int count = authManager->configIds().count();
QCOMPARE( count, 0 );

QFileInfo cfgFileInfo( authFile );
if ( cfgFileInfo.exists() && cfgFileInfo.isFile() )
{
// we still check that the configuration can be imported
bool ok = authManager->importAuthenticationConfigsFromXml( authFile, AUTH_CONFIG_PASSWORD, true );

QCOMPARE( ok, true );
count = authManager->configIds().count();
QCOMPARE( count, 1 );
}
}
5 changes: 5 additions & 0 deletions app/test/testactiveproject.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

#include <QObject>
#include <merginapi.h>
#include <qgsapplication.h>
#include <qgsauthmanager.h>

const QString AUTH_CONFIG_PASSWORD = QStringLiteral( "1234" );

class TestActiveProject : public QObject
{
Expand All @@ -29,6 +33,7 @@ class TestActiveProject : public QObject
void testPositionTrackingFlag();
void testRecordingAllowed();
void testLoadingFlagFileExpiration();
void testLoadingAuthFileFromConfiguration();

private:
MerginApi *mApi;
Expand Down
2 changes: 1 addition & 1 deletion cmake/InstallStep.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ if (WIN)
)
install(
DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin/Qca/crypto/"
DESTINATION .
DESTINATION "./Qca/crypto"
FILES_MATCHING
PATTERN "*.dll"
)
Expand Down
6 changes: 6 additions & 0 deletions core/coreutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
const QString CoreUtils::QSETTINGS_APP_GROUP_NAME = QStringLiteral( "inputApp" );
const QString CoreUtils::LOG_TO_DEVNULL = QStringLiteral();
const QString CoreUtils::LOG_TO_STDOUT = QStringLiteral( "TO_STDOUT" );
const QString CoreUtils::AUTH_CONFIG_FILENAME = QStringLiteral( "qgis_cfg.xml" );
QString CoreUtils::sLogFile = CoreUtils::LOG_TO_DEVNULL;
int CoreUtils::CHECKSUM_CHUNK_SIZE = 65536;

Expand Down Expand Up @@ -263,6 +264,11 @@ bool CoreUtils::hasProjectFileExtension( const QString filePath )
return filePath.contains( ".qgs", Qt::CaseInsensitive ) || filePath.contains( ".qgz", Qt::CaseInsensitive );
}

bool CoreUtils::isAuthConfigFile( const QString filePath )
{
return filePath == AUTH_CONFIG_FILENAME;
}

bool CoreUtils::isValidName( const QString &name )
{
static QRegularExpression reForbiddenmNames( R"([@#$%^&*\(\)\{\}\[\]\\\/\|\+=<>~\?:;,`\'\"]|^[\s^\.].*$|^CON$|^PRN$|^AUX$|^NUL$|^COM\d$|^LPT\d|^support$|^helpdesk$|^merginmaps$|^lutraconsulting$|^mergin$|^lutra$|^input$|^sales$|^admin$)", QRegularExpression::CaseInsensitiveOption );
Expand Down
4 changes: 4 additions & 0 deletions core/coreutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class CoreUtils
*/
static const QString LOG_TO_DEVNULL;
static const QString LOG_TO_STDOUT;
static const QString AUTH_CONFIG_FILENAME;
static void setLogFilename( const QString &value );

static QString logFilename();
Expand All @@ -88,6 +89,9 @@ class CoreUtils
//! Checks whether file path has a QGIS project suffix (qgs or qgz)
static bool hasProjectFileExtension( const QString filePath );

// Checks whether file is the authentication configuration
static bool isAuthConfigFile( const QString filePath );

/**
* Check whether given project/user name is valid
*/
Expand Down
25 changes: 24 additions & 1 deletion core/merginapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,29 @@ bool MerginApi::projectFileHasBeenUpdated( const ProjectDiff &diff )
return false;
}

bool MerginApi::authConfigurationHasChanged( const ProjectDiff &diff )
{
for ( QString filePath : diff.remoteAdded )
{
if ( CoreUtils::isAuthConfigFile( filePath ) )
return true;
}

for ( QString filePath : diff.remoteUpdated )
{
if ( CoreUtils::isAuthConfigFile( filePath ) )
return true;
}

for ( QString filePath : diff.remoteDeleted )
{
if ( CoreUtils::isAuthConfigFile( filePath ) )
return true;
}

return false;
}

bool MerginApi::supportsSelectiveSync() const
{
return mSupportsSelectiveSync;
Expand Down Expand Up @@ -3691,7 +3714,7 @@ void MerginApi::finishProjectSync( const QString &projectFullName, bool syncSucc
ProjectDiff diff = transaction.diff;
int newVersion = syncSuccessful ? transaction.version : -1;

if ( transaction.gpkgSchemaChanged || projectFileHasBeenUpdated( diff ) )
if ( transaction.gpkgSchemaChanged || projectFileHasBeenUpdated( diff ) || authConfigurationHasChanged( diff ) )
{
emit projectReloadNeededAfterSync( projectFullName );
}
Expand Down
2 changes: 2 additions & 0 deletions core/merginapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,8 @@ class MerginApi: public QObject

bool projectFileHasBeenUpdated( const ProjectDiff &diff );

bool authConfigurationHasChanged( const ProjectDiff &diff );

//! Checks if retrieving the project role from the server was successful and
//! if it differs from the current project role, emits a signal with new project role
void reloadProjectRoleReplyFinished();
Expand Down
Binary file not shown.
Binary file added test/test_data/project_auth_file/auth-test.qgz
Binary file not shown.
2 changes: 2 additions & 0 deletions test/test_data/project_auth_file/qgis_cfg.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!DOCTYPE qgis_authentication>
<qgis_authentication civ="fce8ce72133e5d85b5af66d3046e5c78411f20bb3ebb47eb851d469350922773" salt="0bd55a8f76c40cdef3bc8897714175fa" hash="dfe957824c402c5853a7a0f1d0feaa58">21ce1cf53ab351eb229b3ae83f7384b76578dd93d76e3515904213a79dddd46b0ed3e05f5f906fa681e7714af0d0d9dd551735a31bae5ea96333cc9ebe2debc003eebf44e42c1ec5cd9c7214d8a7399dedfd2f89819661615013ec79fb3bce642138f593be11c64f4f1374b274e1b5c887f229190521e7d0a340f97f1a27c78d70f1e3ac28d9b605d0ae07a9832c03ea2778c0629cbbc8e932d5b36b941f6d4eabe0b4d734d10ce41464064da7e2ee14204064a1ddfa7ca9427ad3aa4fd0cc467bab98b12235a867ddc6542935db835b</qgis_authentication>
2 changes: 1 addition & 1 deletion vcpkg/ports/qca/portfile.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ vcpkg_from_github(
REF
"v${VERSION}"
SHA512
956d36058db61498c492fc9b5345b45ca75a5e8214fcbf358273dfacdd5980c1394394652536d9332df05f29fc912d0781338bcca403d98de1285bb8b1216402
21bbc483f78d8c6b99bf2a4375db6a1bcc8a1a16df01e2295dc6a5b43fa27ccbef39114ee33d456071f712a00aca0ab3bc1bf767df82333c2f98ea35f7d35b45
PATCHES
0001-fix-path-for-vcpkg.patch
0002-fix-build-error.patch
Expand Down
Loading
Loading