QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsauthmanager.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsauthmanager.cpp
3 ---------------------
4 begin : October 5, 2014
5 copyright : (C) 2014 by Boundless Spatial, Inc. USA
6 author : Larry Shaffer
7 email : lshaffer at boundlessgeo dot com
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17#include <QCoreApplication>
18#include <QDir>
19#include <QDomDocument>
20#include <QDomElement>
21#include <QEventLoop>
22#include <QFile>
23#include <QFileInfo>
24#include <QMutexLocker>
25#include <QObject>
26#include <QRandomGenerator>
27#include <QRegularExpression>
28#include <QSet>
29#include <QSqlDatabase>
30#include <QSqlDriver>
31#include <QSqlError>
32#include <QSqlQuery>
33#include <QString>
34#include <QTextStream>
35#include <QTime>
36#include <QTimer>
37#include <QVariant>
38
39using namespace Qt::StringLiterals;
40
41#ifdef HAVE_AUTH
42#include <QtCrypto>
43#endif
44
45#ifndef QT_NO_SSL
46#include <QSslConfiguration>
47#endif
48
49// QGIS includes
50#ifdef HAVE_AUTH
51#include "qgsauthcertutils.h"
52#endif
53#include "qgsauthcrypto.h"
54#include "qgsauthmethod.h"
57#include "qgscredentials.h"
58#include "qgslogger.h"
59#include "qgsmessagelog.h"
60#include "qgsauthmanager.h"
61#include "moc_qgsauthmanager.cpp"
64#include "qgsvariantutils.h"
65#include "qgssettings.h"
66#include "qgsruntimeprofiler.h"
68#include "qgssettingstree.h"
69
70QgsAuthManager *QgsAuthManager::sInstance = nullptr;
71
72const QString QgsAuthManager::AUTH_CONFIG_TABLE = u"auth_configs"_s;
73const QString QgsAuthManager::AUTH_SERVERS_TABLE = u"auth_servers"_s;
74const QString QgsAuthManager::AUTH_MAN_TAG = QObject::tr( "Authentication Manager" );
75const QString QgsAuthManager::AUTH_CFG_REGEX = u"authcfg=([a-z]|[A-Z]|[0-9]){7}"_s;
76
77
78const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME_BASE( "QGIS-Master-Password" );
79const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_FOLDER_NAME( "QGIS" );
80
81const QgsSettingsEntryBool *QgsAuthManager::settingsGenerateRandomPasswordForPasswordHelper = new QgsSettingsEntryBool( u"generate-random-password-for-keychain"_s, QgsSettingsTree::sTreeAuthentication, true, u"Whether a random password should be automatically generated for the authentication database and stored in the system keychain."_s );
82const QgsSettingsEntryBool *QgsAuthManager::settingsUsingGeneratedRandomPassword = new QgsSettingsEntryBool( u"using-generated-random-password"_s, QgsSettingsTree::sTreeAuthentication, false, u"True if the user is using an autogenerated random password stored in the system keychain."_s );
83
85#if defined(Q_OS_MAC)
87#elif defined(Q_OS_WIN)
88const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Password Manager" );
89#elif defined(Q_OS_LINUX)
90const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( u"Wallet/KeyRing"_s );
91#else
92const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Password Manager" );
93#endif
95
97{
98#ifdef HAVE_AUTH
99 static QMutex sMutex;
100 QMutexLocker locker( &sMutex );
101 if ( !sInstance )
102 {
103 sInstance = new QgsAuthManager( );
104 }
105 return sInstance;
106#else
107 return new QgsAuthManager();
108#endif
109}
110
111
113{
114#ifdef HAVE_AUTH
115 mMutex = std::make_unique<QRecursiveMutex>();
116 mMasterPasswordMutex = std::make_unique<QRecursiveMutex>();
117 connect( this, &QgsAuthManager::messageLog,
118 this, &QgsAuthManager::writeToConsole );
119#endif
120}
121
123{
124#ifdef HAVE_AUTH
126
127 QSqlDatabase authdb;
128
129 if ( isDisabled() )
130 return authdb;
131
132 // while everything we use from QSqlDatabase here is thread safe, we need to ensure
133 // that the connection cleanup on thread finalization happens in a predictable order
134 QMutexLocker locker( mMutex.get() );
135
136 // Get the first enabled DB storage from the registry
138 {
139 return storage->authDatabaseConnection();
140 }
141
142 return authdb;
143#else
144 return QSqlDatabase();
145#endif
146}
147
149{
150#ifdef HAVE_AUTH
151 if ( ! isDisabled() )
152 {
154
155 // Returns the first enabled and ready "DB" storage
157 const QList<QgsAuthConfigurationStorage *> storages { storageRegistry->readyStorages() };
158 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
159 {
160 if ( auto dbStorage = qobject_cast<QgsAuthConfigurationStorageDb *>( storage ) )
161 {
162 if ( dbStorage->capabilities() & Qgis::AuthConfigurationStorageCapability::ReadConfiguration )
163 {
164 return dbStorage->quotedQualifiedIdentifier( dbStorage->methodConfigTableName() );
165 }
166 }
167 }
168 }
169
170 return QString();
171#else
172 return QString();
173#endif
174}
175
177{
178#ifdef HAVE_AUTH
179 // Loop through all registered SQL drivers and return false if
180 // the URI starts with one of them except the SQLite based drivers
181 const auto drivers { QSqlDatabase::drivers() };
182 for ( const QString &driver : std::as_const( drivers ) )
183 {
184 if ( driver != ( u"QSQLITE"_s ) && driver != ( u"QSPATIALITE"_s ) && uri.startsWith( driver ) )
185 {
186 return false;
187 }
188 }
189 return true;
190#else
191 Q_UNUSED( uri )
192 return false;
193#endif
194}
195
197{
198#ifdef HAVE_AUTH
199 return mAuthDatabaseConnectionUri;
200#else
201 return QString();
202#endif
203}
204
206{
207#ifdef HAVE_AUTH
208 QRegularExpression re( u"password=(.*)"_s );
209 QString uri = mAuthDatabaseConnectionUri;
210 return uri.replace( re, u"password=*****"_s );
211#else
212 return QString();
213#endif
214}
215
216
217bool QgsAuthManager::init( const QString &pluginPath, const QString &authDatabasePath )
218{
219#ifdef HAVE_AUTH
220 mAuthDatabaseConnectionUri = authDatabasePath.startsWith( "QSQLITE://"_L1 ) ? authDatabasePath : u"QSQLITE://"_s + authDatabasePath;
221 return initPrivate( pluginPath );
222#else
223 Q_UNUSED( pluginPath )
224 Q_UNUSED( authDatabasePath )
225 return false;
226#endif
227}
228
230{
231#ifdef HAVE_AUTH
232 static QRecursiveMutex sInitializationMutex;
233 static bool sInitialized = false;
234
235 sInitializationMutex.lock();
236 if ( sInitialized )
237 {
238 sInitializationMutex.unlock();
239 return mLazyInitResult;
240 }
241
242 mLazyInitResult = const_cast< QgsAuthManager * >( this )->initPrivate( mPluginPath );
243 sInitialized = true;
244 sInitializationMutex.unlock();
245
246 return mLazyInitResult;
247#else
248 return false;
249#endif
250}
251
252static char *sPassFileEnv = nullptr;
253
254bool QgsAuthManager::initPrivate( const QString &pluginPath )
255{
256#ifdef HAVE_AUTH
257 if ( mAuthInit )
258 return true;
259
260 mAuthInit = true;
261 QgsScopedRuntimeProfile profile( tr( "Initializing authentication manager" ) );
262
263 QgsDebugMsgLevel( u"Initializing QCA..."_s, 2 );
264 mQcaInitializer = std::make_unique<QCA::Initializer>( QCA::Practical, 256 );
265
266 QgsDebugMsgLevel( u"QCA initialized."_s, 2 );
267 QCA::scanForPlugins();
268
269 QgsDebugMsgLevel( u"QCA Plugin Diagnostics Context: %1"_s.arg( QCA::pluginDiagnosticText() ), 2 );
270 QStringList capabilities;
271
272 capabilities = QCA::supportedFeatures();
273 QgsDebugMsgLevel( u"QCA supports: %1"_s.arg( capabilities.join( "," ) ), 2 );
274
275 // do run-time check for qca-ossl plugin
276 if ( !QCA::isSupported( "cert", u"qca-ossl"_s ) )
277 {
278 mAuthDisabled = true;
279 mAuthDisabledMessage = tr( "QCA's OpenSSL plugin (qca-ossl) is missing" );
280 return isDisabled();
281 }
282
283 QgsDebugMsgLevel( u"Prioritizing qca-ossl over all other QCA providers..."_s, 2 );
284 const QCA::ProviderList provds = QCA::providers();
285 QStringList prlist;
286 for ( QCA::Provider *p : provds )
287 {
288 QString pn = p->name();
289 int pr = 0;
290 if ( pn != "qca-ossl"_L1 )
291 {
292 pr = QCA::providerPriority( pn ) + 1;
293 }
294 QCA::setProviderPriority( pn, pr );
295 prlist << u"%1:%2"_s.arg( pn ).arg( QCA::providerPriority( pn ) );
296 }
297 QgsDebugMsgLevel( u"QCA provider priorities: %1"_s.arg( prlist.join( ", " ) ), 2 );
298
299 QgsDebugMsgLevel( u"Populating auth method registry"_s, 3 );
300 QgsAuthMethodRegistry *authreg = QgsAuthMethodRegistry::instance( pluginPath );
301
302 QStringList methods = authreg->authMethodList();
303
304 QgsDebugMsgLevel( u"Authentication methods found: %1"_s.arg( methods.join( ", " ) ), 2 );
305
306 if ( methods.isEmpty() )
307 {
308 mAuthDisabled = true;
309 mAuthDisabledMessage = tr( "No authentication method plugins found" );
310 return isDisabled();
311 }
312
314 {
315 mAuthDisabled = true;
316 mAuthDisabledMessage = tr( "No authentication method plugins could be loaded" );
317 return isDisabled();
318 }
319
320 QgsDebugMsgLevel( u"Auth database URI: %1"_s.arg( mAuthDatabaseConnectionUri ), 2 );
321
322 // Add the default configuration storage
323 const QString sqliteDbPath { sqliteDatabasePath() };
324 if ( ! sqliteDbPath.isEmpty() )
325 {
326 authConfigurationStorageRegistry()->addStorage( new QgsAuthConfigurationStorageSqlite( sqliteDbPath ) );
327 }
328 else if ( ! mAuthDatabaseConnectionUri.isEmpty() )
329 {
330 // For safety reasons we don't allow writing on potentially shared storages by default, plugins may override
331 // this behavior by registering their own storage subclass or by explicitly setting read-only to false.
332 QgsAuthConfigurationStorageDb *storage = new QgsAuthConfigurationStorageDb( mAuthDatabaseConnectionUri );
333 if ( !QgsAuthManager::isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
334 {
335 storage->setReadOnly( true );
336 }
338 }
339
340 // Loop through all registered storages and call initialize
341 const QList<QgsAuthConfigurationStorage *> storages { authConfigurationStorageRegistry()->storages() };
342 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
343 {
344 if ( ! storage->isEnabled() )
345 {
346 QgsDebugMsgLevel( u"Storage %1 is disabled"_s.arg( storage->name() ), 2 );
347 continue;
348 }
349 if ( !storage->initialize() )
350 {
351 const QString err = tr( "Failed to initialize storage %1: %2" ).arg( storage->name(), storage->lastError() );
352 QgsDebugError( err );
354 }
355 else
356 {
357 QgsDebugMsgLevel( u"Storage %1 initialized"_s.arg( storage->name() ), 2 );
358 }
359 connect( storage, &QgsAuthConfigurationStorage::methodConfigChanged, this, [this] { updateConfigAuthMethods(); } );
361 }
362
364
365#ifndef QT_NO_SSL
367#endif
368 // set the master password from first line of file defined by QGIS_AUTH_PASSWORD_FILE env variable
369 if ( sPassFileEnv && masterPasswordHashInDatabase() )
370 {
371 QString passpath( sPassFileEnv );
372 free( sPassFileEnv );
373 sPassFileEnv = nullptr;
374
375 QString masterpass;
376 QFile passfile( passpath );
377 if ( passfile.exists() && passfile.open( QIODevice::ReadOnly | QIODevice::Text ) )
378 {
379 QTextStream passin( &passfile );
380 while ( !passin.atEnd() )
381 {
382 masterpass = passin.readLine();
383 break;
384 }
385 passfile.close();
386 }
387 if ( !masterpass.isEmpty() )
388 {
389 if ( setMasterPassword( masterpass, true ) )
390 {
391 QgsDebugMsgLevel( u"Authentication master password set from QGIS_AUTH_PASSWORD_FILE"_s, 2 );
392 }
393 else
394 {
395 QgsDebugError( "QGIS_AUTH_PASSWORD_FILE set, but FAILED to set password using: " + passpath );
396 return false;
397 }
398 }
399 else
400 {
401 QgsDebugError( "QGIS_AUTH_PASSWORD_FILE set, but FAILED to read password from: " + passpath );
402 return false;
403 }
404 }
405
406#ifndef QT_NO_SSL
408#endif
409
410 return true;
411#else
412 Q_UNUSED( pluginPath )
413 return false;
414#endif
415}
416
417void QgsAuthManager::setup( const QString &pluginPath, const QString &authDatabasePath )
418{
419#ifdef HAVE_AUTH
420 mPluginPath = pluginPath;
421 mAuthDatabaseConnectionUri = authDatabasePath;
422
423 const char *p = getenv( "QGIS_AUTH_PASSWORD_FILE" );
424 if ( p )
425 {
426 sPassFileEnv = qstrdup( p );
427
428 // clear the env variable, so it can not be accessed from plugins, etc.
429 // (note: stored QgsApplication::systemEnvVars() skips this env variable as well)
430#ifdef Q_OS_WIN
431 putenv( "QGIS_AUTH_PASSWORD_FILE" );
432#else
433 unsetenv( "QGIS_AUTH_PASSWORD_FILE" );
434#endif
435 }
436#else
437 Q_UNUSED( pluginPath )
438 Q_UNUSED( authDatabasePath )
439#endif
440}
441
442QString QgsAuthManager::generatePassword()
443{
444#ifdef HAVE_AUTH
445 QRandomGenerator generator = QRandomGenerator::securelySeeded();
446 QString pw;
447 pw.resize( 32 );
448 static const QString sPwChars = u"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_-{}[]"_s;
449 for ( int i = 0; i < pw.size(); ++i )
450 {
451 pw[i] = sPwChars.at( generator.bounded( 0, sPwChars.length() ) );
452 }
453 return pw;
454#else
455 return QString();
456#endif
457}
458
460{
461#ifdef HAVE_AUTH
463
464 if ( mAuthDisabled )
465 {
466 QgsDebugError( u"Authentication system DISABLED: QCA's qca-ossl (OpenSSL) plugin is missing"_s );
467 }
468 return mAuthDisabled;
469#else
470 return false;
471#endif
472}
473
475{
476#ifdef HAVE_AUTH
478
479 return tr( "Authentication system is DISABLED:\n%1" ).arg( mAuthDisabledMessage );
480#else
481 return QString();
482#endif
483}
484
486{
487#ifdef HAVE_AUTH
488 QMutexLocker locker( mMasterPasswordMutex.get() );
489 if ( isDisabled() )
490 return false;
491
492 if ( mScheduledDbErase )
493 return false;
494
495 if ( !passwordHelperEnabled() )
496 return false;
497
498 if ( !mMasterPass.isEmpty() )
499 {
500 QgsDebugError( u"Master password is already set!"_s );
501 return false;
502 }
503
504 const QString newPassword = generatePassword();
505 if ( passwordHelperWrite( newPassword ) )
506 {
507 mMasterPass = newPassword;
508 }
509 else
510 {
511 emit passwordHelperMessageLog( tr( "Master password could not be written to the %1" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
512 return false;
513 }
514
515 if ( !verifyMasterPassword() )
516 {
517 emit passwordHelperMessageLog( tr( "Master password was written to the %1 but could not be verified" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
518 return false;
519 }
520
521 QgsDebugMsgLevel( u"Master password is set and verified"_s, 2 );
522 settingsUsingGeneratedRandomPassword->setValue( true );
523 return true;
524#else
525 return false;
526#endif
527}
528
529
531{
532#ifdef HAVE_AUTH
533 if ( !QgsAuthManager::isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
534 {
535 return QString();
536 }
537
538 // Remove the driver:// prefix if present
539 QString path = mAuthDatabaseConnectionUri;
540 if ( path.startsWith( u"QSQLITE://"_s, Qt::CaseSensitivity::CaseInsensitive ) )
541 {
542 path = path.mid( 10 );
543 }
544 else if ( path.startsWith( u"QSPATIALITE://"_s, Qt::CaseSensitivity::CaseInsensitive ) )
545 {
546 path = path.mid( 14 );
547 }
548
549 return QDir::cleanPath( path );
550#else
551 return QString();
552#endif
553}
554
556{
557#ifdef HAVE_AUTH
558 return sqliteDatabasePath();
559#else
560 return QString();
561#endif
562}
563
565{
566#ifdef HAVE_AUTH
568
569 QMutexLocker locker( mMasterPasswordMutex.get() );
570 if ( isDisabled() )
571 return false;
572
573 if ( mScheduledDbErase )
574 return false;
575
576 if ( mMasterPass.isEmpty() )
577 {
578 QgsDebugMsgLevel( u"Master password is not yet set by user"_s, 2 );
579 if ( !masterPasswordInput() )
580 {
581 QgsDebugMsgLevel( u"Master password input canceled by user"_s, 2 );
582 return false;
583 }
584 }
585 else
586 {
587 QgsDebugMsgLevel( u"Master password is set"_s, 2 );
588 if ( !verify )
589 return true;
590 }
591
592 if ( !verifyMasterPassword() )
593 return false;
594
595 QgsDebugMsgLevel( u"Master password is set and verified"_s, 2 );
596 return true;
597#else
598 Q_UNUSED( verify )
599 return false;
600#endif
601}
602
603bool QgsAuthManager::setMasterPassword( const QString &pass, bool verify )
604{
605#ifdef HAVE_AUTH
607
608 QMutexLocker locker( mMutex.get() );
609 if ( isDisabled() )
610 return false;
611
612 if ( mScheduledDbErase )
613 return false;
614
615 // since this is generally for automation, we don't care if passed-in is same as existing
616 QString prevpass = QString( mMasterPass );
617 mMasterPass = pass;
618 if ( verify && !verifyMasterPassword() )
619 {
620 mMasterPass = prevpass;
621 const char *err = QT_TR_NOOP( "Master password set: FAILED to verify, reset to previous" );
622 QgsDebugError( err );
624 return false;
625 }
626
627 QgsDebugMsgLevel( u"Master password set: SUCCESS%1"_s.arg( verify ? " and verified" : "" ), 2 );
628 return true;
629#else
630 Q_UNUSED( pass )
631 Q_UNUSED( verify )
632 return false;
633#endif
634}
635
636bool QgsAuthManager::verifyMasterPassword( const QString &compare )
637{
638#ifdef HAVE_AUTH
640
641 if ( isDisabled() )
642 return false;
643
644 int rows = 0;
645 if ( !masterPasswordRowsInDb( rows ) )
646 {
647 const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
648 QgsDebugError( err );
650
652 return false;
653 }
654
655 QgsDebugMsgLevel( u"Master password: %1 rows in database"_s.arg( rows ), 2 );
656
657 if ( rows > 1 )
658 {
659 const char *err = QT_TR_NOOP( "Master password: FAILED to find just one master password record in database" );
660 QgsDebugError( err );
662
664 return false;
665 }
666 else if ( rows == 1 )
667 {
668 if ( !masterPasswordCheckAgainstDb( compare ) )
669 {
670 if ( compare.isNull() ) // don't complain when comparing, since it could be an incomplete comparison string
671 {
672 const char *err = QT_TR_NOOP( "Master password: FAILED to verify against hash in database" );
673 QgsDebugError( err );
675
677
678 emit masterPasswordVerified( false );
679 }
680 ++mPassTries;
681 if ( mPassTries >= 5 )
682 {
683 mAuthDisabled = true;
684 const char *err = QT_TR_NOOP( "Master password: failed 5 times authentication system DISABLED" );
685 QgsDebugError( err );
687 }
688 return false;
689 }
690 else
691 {
692 QgsDebugMsgLevel( u"Master password: verified against hash in database"_s, 2 );
693 if ( compare.isNull() )
694 emit masterPasswordVerified( true );
695 }
696 }
697 else if ( compare.isNull() ) // compares should never be stored
698 {
699 if ( !masterPasswordStoreInDb() )
700 {
701 const char *err = QT_TR_NOOP( "Master password: hash FAILED to be stored in database" );
702 QgsDebugError( err );
704
706 return false;
707 }
708 else
709 {
710 QgsDebugMsgLevel( u"Master password: hash stored in database"_s, 2 );
711 }
712 // double-check storing
713 if ( !masterPasswordCheckAgainstDb() )
714 {
715 const char *err = QT_TR_NOOP( "Master password: FAILED to verify against hash in database" );
716 QgsDebugError( err );
718
720 emit masterPasswordVerified( false );
721 return false;
722 }
723 else
724 {
725 QgsDebugMsgLevel( u"Master password: verified against hash in database"_s, 2 );
726 emit masterPasswordVerified( true );
727 }
728 }
729
730 return true;
731#else
732 Q_UNUSED( compare )
733 return false;
734#endif
735}
736
738{
739#ifdef HAVE_AUTH
741
742 return !mMasterPass.isEmpty();
743#else
744 return false;
745#endif
746}
747
748bool QgsAuthManager::masterPasswordSame( const QString &pass ) const
749{
750#ifdef HAVE_AUTH
752
753 return mMasterPass == pass;
754#else
755 Q_UNUSED( pass )
756 return false;
757#endif
758}
759
760bool QgsAuthManager::resetMasterPassword( const QString &newpass, const QString &oldpass,
761 bool keepbackup, QString *backuppath )
762{
763#ifdef HAVE_AUTH
765
766 if ( isDisabled() )
767 return false;
768
769 // verify caller knows the current master password
770 // this means that the user will have had to already set the master password as well
771 if ( !masterPasswordSame( oldpass ) )
772 return false;
773
774 QString dbbackup;
775 if ( !backupAuthenticationDatabase( &dbbackup ) )
776 return false;
777
778 QgsDebugMsgLevel( u"Master password reset: backed up current database"_s, 2 );
779
780 // store current password and civ
781 QString prevpass = QString( mMasterPass );
782 QString prevciv = QString( masterPasswordCiv() );
783
784 // on ANY FAILURE from this point, reinstate previous password and database
785 bool ok = true;
786
787 // clear password hash table (also clears mMasterPass)
788 if ( ok && !masterPasswordClearDb() )
789 {
790 ok = false;
791 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not clear current password from database" );
792 QgsDebugError( err );
794 }
795 if ( ok )
796 {
797 QgsDebugMsgLevel( u"Master password reset: cleared current password from database"_s, 2 );
798 }
799
800 // mMasterPass empty, set new password (don't verify, since not stored yet)
801 setMasterPassword( newpass, false );
802
803 // store new password hash
804 if ( ok && !masterPasswordStoreInDb() )
805 {
806 ok = false;
807 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not store new password in database" );
808 QgsDebugError( err );
810 }
811 if ( ok )
812 {
813 QgsDebugMsgLevel( u"Master password reset: stored new password in database"_s, 2 );
814 }
815
816 // verify it stored password properly
817 if ( ok && !verifyMasterPassword() )
818 {
819 ok = false;
820 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not verify new password in database" );
821 QgsDebugError( err );
823 }
824
825 // re-encrypt everything with new password
826 if ( ok && !reencryptAllAuthenticationConfigs( prevpass, prevciv ) )
827 {
828 ok = false;
829 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt configs in database" );
830 QgsDebugError( err );
832 }
833 if ( ok )
834 {
835 QgsDebugMsgLevel( u"Master password reset: re-encrypted configs in database"_s, 2 );
836 }
837
838 // verify it all worked
839 if ( ok && !verifyPasswordCanDecryptConfigs() )
840 {
841 ok = false;
842 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not verify password can decrypt re-encrypted configs" );
843 QgsDebugError( err );
845 }
846
847 if ( ok && !reencryptAllAuthenticationSettings( prevpass, prevciv ) )
848 {
849 ok = false;
850 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt settings in database" );
851 QgsDebugError( err );
853 }
854
855 if ( ok && !reencryptAllAuthenticationIdentities( prevpass, prevciv ) )
856 {
857 ok = false;
858 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt identities in database" );
859 QgsDebugError( err );
861 }
862
863 if ( qgetenv( "QGIS_CONTINUOUS_INTEGRATION_RUN" ) != u"true"_s && passwordHelperEnabled() && !passwordHelperSync() )
864 {
865 ok = false;
866 const QString err = tr( "Master password reset FAILED: could not sync password helper: %1" ).arg( passwordHelperErrorMessage() );
867 QgsDebugError( err );
869 }
870
871 // something went wrong, reinstate previous password and database
872 if ( !ok )
873 {
874 // backup database of failed attempt, for inspection
875 QString errdbbackup( dbbackup );
876 errdbbackup.replace( ".db"_L1, "_ERROR.db"_L1 );
877 QFile::rename( sqliteDatabasePath(), errdbbackup );
878 QgsDebugError( u"Master password reset FAILED: backed up failed db at %1"_s.arg( errdbbackup ) );
879 // reinstate previous database and password
880 QFile::rename( dbbackup, sqliteDatabasePath() );
881 mMasterPass = prevpass;
882 QgsDebugError( u"Master password reset FAILED: reinstated previous password and database"_s );
883
884 // assign error db backup
885 if ( backuppath )
886 *backuppath = errdbbackup;
887
888 return false;
889 }
890
891 if ( !keepbackup && !QFile::remove( dbbackup ) )
892 {
893 const char *err = QT_TR_NOOP( "Master password reset: could not remove old database backup" );
894 QgsDebugError( err );
896 // a non-blocking error, continue
897 }
898
899 if ( keepbackup )
900 {
901 QgsDebugMsgLevel( u"Master password reset: backed up previous db at %1"_s.arg( dbbackup ), 2 );
902 if ( backuppath )
903 *backuppath = dbbackup;
904 }
905
906 settingsUsingGeneratedRandomPassword->setValue( false );
907
908 QgsDebugMsgLevel( u"Master password reset: SUCCESS"_s, 2 );
909 emit authDatabaseChanged();
910 return true;
911#else
912 Q_UNUSED( newpass )
913 Q_UNUSED( oldpass )
914 Q_UNUSED( keepbackup )
915 Q_UNUSED( backuppath )
916 return false;
917#endif
918}
919
920bool QgsAuthManager::resetMasterPasswordUsingStoredPasswordHelper( const QString &newPassword, bool keepBackup, QString *backupPath )
921{
922#ifdef HAVE_AUTH
924 {
925 emit passwordHelperMessageLog( tr( "Master password stored in your %1 is not valid" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
926 return false;
927 }
928
929 bool readOk = false;
930 const QString existingPassword = passwordHelperRead( readOk );
931 if ( !readOk )
932 {
933 emit passwordHelperMessageLog( tr( "Master password could not be read from the %1" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
934 return false;
935 }
936
937 return resetMasterPassword( newPassword, existingPassword, keepBackup, backupPath );
938#else
939 Q_UNUSED( newPassword )
940 Q_UNUSED( keepBackup )
941 Q_UNUSED( backupPath )
942 return false;
943#endif
944}
945
947{
948#ifdef HAVE_AUTH
950
951 mScheduledDbErase = scheduleErase;
952 // any call (start or stop) should reset these
953 mScheduledDbEraseRequestEmitted = false;
954 mScheduledDbEraseRequestCount = 0;
955
956 if ( scheduleErase )
957 {
958 if ( !mScheduledDbEraseTimer )
959 {
960 mScheduledDbEraseTimer = std::make_unique<QTimer>( this );
961 connect( mScheduledDbEraseTimer.get(), &QTimer::timeout, this, &QgsAuthManager::tryToStartDbErase );
962 mScheduledDbEraseTimer->start( mScheduledDbEraseRequestWait * 1000 );
963 }
964 else if ( !mScheduledDbEraseTimer->isActive() )
965 {
966 mScheduledDbEraseTimer->start();
967 }
968 }
969 else
970 {
971 if ( mScheduledDbEraseTimer && mScheduledDbEraseTimer->isActive() )
972 mScheduledDbEraseTimer->stop();
973 }
974#else
975 Q_UNUSED( scheduleErase )
976#endif
977}
978
980{
981#ifdef HAVE_AUTH
982 if ( isDisabled() )
983 return false;
984
985 qDeleteAll( mAuthMethods );
986 mAuthMethods.clear();
987 const QStringList methods = QgsAuthMethodRegistry::instance()->authMethodList();
988 for ( const auto &authMethodKey : methods )
989 {
990 mAuthMethods.insert( authMethodKey, QgsAuthMethodRegistry::instance()->createAuthMethod( authMethodKey ) );
991 }
992
993 return !mAuthMethods.isEmpty();
994#else
995 return false;
996#endif
997}
998
1000{
1001#ifdef HAVE_AUTH
1003
1004 QStringList configids = configIds();
1005 QString id;
1006 int len = 7;
1007
1008 // Suppress warning: Potential leak of memory in qtimer.h [clang-analyzer-cplusplus.NewDeleteLeaks]
1009#ifndef __clang_analyzer__
1010 // sleep just a bit to make sure the current time has changed
1011 QEventLoop loop;
1012 QTimer::singleShot( 3, &loop, &QEventLoop::quit );
1013 loop.exec();
1014#endif
1015
1016 while ( true )
1017 {
1018 id.clear();
1019 for ( int i = 0; i < len; i++ )
1020 {
1021 switch ( QRandomGenerator::system()->generate() % 2 )
1022 {
1023 case 0:
1024 id += static_cast<char>( '0' + QRandomGenerator::system()->generate() % 10 );
1025 break;
1026 case 1:
1027 id += static_cast<char>( 'a' + QRandomGenerator::system()->generate() % 26 );
1028 break;
1029 }
1030 }
1031 if ( !configids.contains( id ) )
1032 {
1033 break;
1034 }
1035 }
1036 QgsDebugMsgLevel( u"Generated unique ID: %1"_s.arg( id ), 2 );
1037 return id;
1038#else
1039 return QString();
1040#endif
1041}
1042
1043bool QgsAuthManager::configIdUnique( const QString &id ) const
1044{
1045#ifdef HAVE_AUTH
1047
1048 if ( isDisabled() )
1049 return false;
1050
1051 if ( id.isEmpty() )
1052 {
1053 const char *err = QT_TR_NOOP( "Config ID is empty" );
1054 QgsDebugError( err );
1056 return false;
1057 }
1058 QStringList configids = configIds();
1059 return !configids.contains( id );
1060#else
1061 Q_UNUSED( id )
1062 return false;
1063#endif
1064}
1065
1066bool QgsAuthManager::hasConfigId( const QString &txt )
1067{
1068#ifdef HAVE_AUTH
1069 const thread_local QRegularExpression authCfgRegExp( AUTH_CFG_REGEX );
1070 return txt.indexOf( authCfgRegExp ) != -1;
1071#else
1072 Q_UNUSED( txt )
1073 return false;
1074#endif
1075}
1076
1078{
1079#ifdef HAVE_AUTH
1081
1082 QMutexLocker locker( mMutex.get() );
1083 QStringList providerAuthMethodsKeys;
1084 if ( !dataprovider.isEmpty() )
1085 {
1086 providerAuthMethodsKeys = authMethodsKeys( dataprovider.toLower() );
1087 }
1088
1089 QgsAuthMethodConfigsMap baseConfigs;
1090
1091 if ( isDisabled() )
1092 return baseConfigs;
1093
1094 // Loop through all storages with capability ReadConfiguration and get the auth methods
1096 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1097 {
1098 QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
1099 for ( const QgsAuthMethodConfig &config : std::as_const( configs ) )
1100 {
1101 if ( providerAuthMethodsKeys.isEmpty() || providerAuthMethodsKeys.contains( config.method() ) )
1102 {
1103 // Check if the config with that id is already in the list and warn if it is
1104 if ( baseConfigs.contains( config.id() ) )
1105 {
1106 // This may not be an error, since the same config may be stored in multiple storages.
1107 emit messageLog( tr( "A config with same id %1 was already added, skipping from %2" ).arg( config.id(), storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
1108 }
1109 else
1110 {
1111 baseConfigs.insert( config.id(), config );
1112 }
1113 }
1114 }
1115 }
1116
1117 if ( storages.empty() )
1118 {
1119 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1120 QgsDebugError( u"No credentials storages found"_s );
1121 }
1122
1123 return baseConfigs;
1124
1125#else
1126 Q_UNUSED( dataprovider )
1127 return QgsAuthMethodConfigsMap();
1128#endif
1129}
1130
1132{
1133#ifdef HAVE_AUTH
1135
1136 // Loop through all registered storages and get the auth methods
1138 QStringList configIds;
1139 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1140 {
1141 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
1142 for ( const QgsAuthMethodConfig &config : std::as_const( configs ) )
1143 {
1144 if ( ! configIds.contains( config.id() ) )
1145 {
1146 mConfigAuthMethods.insert( config.id(), config.method() );
1147 QgsDebugMsgLevel( u"Stored auth config/methods:\n%1 %2"_s.arg( config.id(), config.method() ), 2 );
1148 }
1149 else
1150 {
1151 // This may not be an error, since the same config may be stored in multiple storages.
1152 // A warning is issued when creating the list initially from availableAuthMethodConfigs()
1153 QgsDebugMsgLevel( u"A config with same id %1 was already added, skipping from %2"_s.arg( config.id(), storage->name() ), 2 );
1154 }
1155 }
1156 }
1157#endif
1158}
1159
1161{
1162#ifdef HAVE_AUTH
1164
1165 if ( isDisabled() )
1166 return nullptr;
1167
1168 if ( !mConfigAuthMethods.contains( authcfg ) )
1169 {
1170 QgsDebugError( u"No config auth method found in database for authcfg: %1"_s.arg( authcfg ) );
1171 return nullptr;
1172 }
1173
1174 QString authMethodKey = mConfigAuthMethods.value( authcfg );
1175
1176 return authMethod( authMethodKey );
1177#else
1178 Q_UNUSED( authcfg )
1179 return nullptr;
1180#endif
1181}
1182
1183QString QgsAuthManager::configAuthMethodKey( const QString &authcfg ) const
1184{
1185#ifdef HAVE_AUTH
1187
1188 if ( isDisabled() )
1189 return QString();
1190
1191 return mConfigAuthMethods.value( authcfg, QString() );
1192#else
1193 Q_UNUSED( authcfg )
1194 return QString();
1195#endif
1196}
1197
1198
1199QStringList QgsAuthManager::authMethodsKeys( const QString &dataprovider )
1200{
1201#ifdef HAVE_AUTH
1203
1204 return authMethodsMap( dataprovider.toLower() ).keys();
1205#else
1206 Q_UNUSED( dataprovider )
1207 return QStringList();
1208#endif
1209}
1210
1211QgsAuthMethod *QgsAuthManager::authMethod( const QString &authMethodKey )
1212{
1213#ifdef HAVE_AUTH
1215
1216 if ( !mAuthMethods.contains( authMethodKey ) )
1217 {
1218 QgsDebugError( u"No auth method registered for auth method key: %1"_s.arg( authMethodKey ) );
1219 return nullptr;
1220 }
1221
1222 return mAuthMethods.value( authMethodKey );
1223#else
1224 Q_UNUSED( authMethodKey )
1225 return nullptr;
1226#endif
1227}
1228
1229const QgsAuthMethodMetadata *QgsAuthManager::authMethodMetadata( const QString &authMethodKey )
1230{
1231#ifdef HAVE_AUTH
1233
1234 if ( !mAuthMethods.contains( authMethodKey ) )
1235 {
1236 QgsDebugError( u"No auth method registered for auth method key: %1"_s.arg( authMethodKey ) );
1237 return nullptr;
1238 }
1239
1240 return QgsAuthMethodRegistry::instance()->authMethodMetadata( authMethodKey );
1241#else
1242 Q_UNUSED( authMethodKey )
1243 return nullptr;
1244#endif
1245}
1246
1247
1249{
1250#ifdef HAVE_AUTH
1252
1253 if ( dataprovider.isEmpty() )
1254 {
1255 return mAuthMethods;
1256 }
1257
1258 QgsAuthMethodsMap filteredmap;
1259 QgsAuthMethodsMap::const_iterator i = mAuthMethods.constBegin();
1260 while ( i != mAuthMethods.constEnd() )
1261 {
1262 if ( i.value()
1263 && ( i.value()->supportedDataProviders().contains( u"all"_s )
1264 || i.value()->supportedDataProviders().contains( dataprovider ) ) )
1265 {
1266 filteredmap.insert( i.key(), i.value() );
1267 }
1268 ++i;
1269 }
1270 return filteredmap;
1271#else
1272 Q_UNUSED( dataprovider )
1273 return QgsAuthMethodsMap();
1274#endif
1275}
1276
1277#ifdef HAVE_GUI
1278QWidget *QgsAuthManager::authMethodEditWidget( const QString &authMethodKey, QWidget *parent )
1279{
1281
1282 QgsAuthMethod *method = authMethod( authMethodKey );
1283 if ( method )
1284 return method->editWidget( parent );
1285 else
1286 return nullptr;
1287}
1288#endif
1289
1291{
1292#ifdef HAVE_AUTH
1294
1295 if ( isDisabled() )
1297
1298 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1299 if ( authmethod )
1300 {
1301 return authmethod->supportedExpansions();
1302 }
1304#else
1305 Q_UNUSED( authcfg )
1306
1308#endif
1309}
1310
1312{
1313#ifdef HAVE_AUTH
1315
1316 QMutexLocker locker( mMutex.get() );
1317 if ( !setMasterPassword( true ) )
1318 return false;
1319
1320 // don't need to validate id, since it has not be defined yet
1321 if ( !config.isValid() )
1322 {
1323 const char *err = QT_TR_NOOP( "Store config: FAILED because config is invalid" );
1324 QgsDebugError( err );
1326 return false;
1327 }
1328
1329 QString uid = config.id();
1330 bool passedinID = !uid.isEmpty();
1331 if ( uid.isEmpty() )
1332 {
1333 uid = uniqueConfigId();
1334 }
1335 else if ( configIds().contains( uid ) )
1336 {
1337 if ( !overwrite )
1338 {
1339 const char *err = QT_TR_NOOP( "Store config: FAILED because pre-defined config ID %1 is not unique" );
1340 QgsDebugError( err );
1342 return false;
1343 }
1344 locker.unlock();
1345 if ( ! removeAuthenticationConfig( uid ) )
1346 {
1347 const char *err = QT_TR_NOOP( "Store config: FAILED because pre-defined config ID %1 could not be removed" );
1348 QgsDebugError( err );
1350 return false;
1351 }
1352 locker.relock();
1353 }
1354
1355 QString configstring = config.configString();
1356 if ( configstring.isEmpty() )
1357 {
1358 const char *err = QT_TR_NOOP( "Store config: FAILED because config string is empty" );
1359 QgsDebugError( err );
1361 return false;
1362 }
1363
1364 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateConfiguration ) )
1365 {
1366 if ( defaultStorage->isEncrypted() )
1367 {
1368 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
1369 }
1370
1371 // Make a copy to not alter the original config
1372 QgsAuthMethodConfig configCopy { config };
1373 configCopy.setId( uid );
1374 if ( !defaultStorage->storeMethodConfig( configCopy, configstring ) )
1375 {
1376 emit messageLog( tr( "Store config: FAILED to store config in default storage: %1" ).arg( defaultStorage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
1377 return false;
1378 }
1379 }
1380 else
1381 {
1382 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1383 return false;
1384 }
1385
1386 // passed-in config should now be like as if it was just loaded from db
1387 if ( !passedinID )
1388 config.setId( uid );
1389
1391
1392 QgsDebugMsgLevel( u"Store config SUCCESS for authcfg: %1"_s.arg( uid ), 2 );
1393 return true;
1394#else
1395 Q_UNUSED( config )
1396 Q_UNUSED( overwrite )
1397 return false;
1398#endif
1399}
1400
1402{
1403#ifdef HAVE_AUTH
1405
1406 QMutexLocker locker( mMutex.get() );
1407 if ( !setMasterPassword( true ) )
1408 return false;
1409
1410 // validate id
1411 if ( !config.isValid( true ) )
1412 {
1413 const char *err = QT_TR_NOOP( "Update config: FAILED because config is invalid" );
1414 QgsDebugError( err );
1416 return false;
1417 }
1418
1419 QString configstring = config.configString();
1420 if ( configstring.isEmpty() )
1421 {
1422 const char *err = QT_TR_NOOP( "Update config: FAILED because config is empty" );
1423 QgsDebugError( err );
1425 return false;
1426 }
1427
1428 // Loop through all storages with capability ReadConfiguration and update the first one that has the config
1430
1431 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1432 {
1433 if ( storage->methodConfigExists( config.id() ) )
1434 {
1436 {
1437 emit messageLog( tr( "Update config: FAILED because storage %1 does not support updating" ).arg( storage->name( ) ), authManTag(), Qgis::MessageLevel::Warning );
1438 return false;
1439 }
1440 if ( storage->isEncrypted() )
1441 {
1442 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
1443 }
1444 if ( !storage->storeMethodConfig( config, configstring ) )
1445 {
1446 emit messageLog( tr( "Store config: FAILED to store config in the storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1447 return false;
1448 }
1449 break;
1450 }
1451 }
1452
1453 if ( storages.empty() )
1454 {
1455 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1456 return false;
1457 }
1458
1459 // should come before updating auth methods, in case user switched auth methods in config
1460 clearCachedConfig( config.id() );
1461
1463
1464 QgsDebugMsgLevel( u"Update config SUCCESS for authcfg: %1"_s.arg( config.id() ), 2 );
1465
1466 return true;
1467#else
1468 Q_UNUSED( config )
1469 return false;
1470#endif
1471}
1472
1473bool QgsAuthManager::loadAuthenticationConfig( const QString &authcfg, QgsAuthMethodConfig &config, bool full )
1474{
1475#ifdef HAVE_AUTH
1477
1478 if ( isDisabled() )
1479 return false;
1480
1481 if ( full && !setMasterPassword( true ) )
1482 return false;
1483
1484 QMutexLocker locker( mMutex.get() );
1485
1486 // Loop through all storages with capability ReadConfiguration and get the config from the first one that has the config
1488
1489 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1490 {
1491 if ( storage->methodConfigExists( authcfg ) )
1492 {
1493 QString payload;
1494 config = storage->loadMethodConfig( authcfg, payload, full );
1495
1496 if ( ! config.isValid( true ) || ( full && payload.isEmpty() ) )
1497 {
1498 emit messageLog( tr( "Load config: FAILED to load config %1 from default storage: %2" ).arg( authcfg, storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1499 return false;
1500 }
1501
1502 if ( full )
1503 {
1504 if ( storage->isEncrypted() )
1505 {
1506 payload = QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), payload );
1507 }
1508 config.loadConfigString( payload );
1509 }
1510
1511 QString authMethodKey = configAuthMethodKey( authcfg );
1512 QgsAuthMethod *authmethod = authMethod( authMethodKey );
1513 if ( authmethod )
1514 {
1515 authmethod->updateMethodConfig( config );
1516 }
1517 else
1518 {
1519 QgsDebugError( u"Update of authcfg %1 FAILED for auth method %2"_s.arg( authcfg, authMethodKey ) );
1520 }
1521
1522 QgsDebugMsgLevel( u"Load %1 config SUCCESS for authcfg: %2"_s.arg( full ? "full" : "base", authcfg ), 2 );
1523 return true;
1524 }
1525 }
1526
1527 if ( storages.empty() )
1528 {
1529 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1530 }
1531 else
1532 {
1533 emit messageLog( tr( "Load config: FAILED to load config %1 from any storage" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
1534 }
1535
1536 return false;
1537#else
1538 Q_UNUSED( authcfg )
1539 Q_UNUSED( config )
1540 Q_UNUSED( full )
1541 return false;
1542#endif
1543}
1544
1546{
1547#ifdef HAVE_AUTH
1549
1550 QMutexLocker locker( mMutex.get() );
1551 if ( isDisabled() )
1552 return false;
1553
1554 if ( authcfg.isEmpty() )
1555 return false;
1556
1557 // Loop through all storages with capability DeleteConfiguration and delete the first one that has the config
1559
1560 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1561 {
1562 if ( storage->methodConfigExists( authcfg ) )
1563 {
1564 if ( !storage->removeMethodConfig( authcfg ) )
1565 {
1566 emit messageLog( tr( "Remove config: FAILED to remove config from the storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1567 return false;
1568 }
1569 else
1570 {
1571 clearCachedConfig( authcfg );
1573 QgsDebugMsgLevel( u"REMOVED config for authcfg: %1"_s.arg( authcfg ), 2 );
1574 return true;
1575 }
1576 break;
1577 }
1578 }
1579
1580 if ( storages.empty() )
1581 {
1582 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1583 }
1584 else
1585 {
1586 emit messageLog( tr( "Remove config: FAILED to remove config %1 from any storage" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
1587 }
1588
1589 return false;
1590
1591#else
1592 Q_UNUSED( authcfg )
1593 return false;
1594#endif
1595}
1596
1597bool QgsAuthManager::exportAuthenticationConfigsToXml( const QString &filename, const QStringList &authcfgs, const QString &password )
1598{
1599#ifdef HAVE_AUTH
1601
1602 if ( filename.isEmpty() )
1603 return false;
1604
1605 QDomDocument document( u"qgis_authentication"_s );
1606 QDomElement root = document.createElement( u"qgis_authentication"_s );
1607 document.appendChild( root );
1608
1609 QString civ;
1610 if ( !password.isEmpty() )
1611 {
1612 QString salt;
1613 QString hash;
1614 QgsAuthCrypto::passwordKeyHash( password, &salt, &hash, &civ );
1615 root.setAttribute( u"salt"_s, salt );
1616 root.setAttribute( u"hash"_s, hash );
1617 root.setAttribute( u"civ"_s, civ );
1618 }
1619
1620 QDomElement configurations = document.createElement( u"configurations"_s );
1621 for ( const QString &authcfg : authcfgs )
1622 {
1623 QgsAuthMethodConfig authMethodConfig;
1624
1625 bool ok = loadAuthenticationConfig( authcfg, authMethodConfig, true );
1626 if ( ok )
1627 {
1628 authMethodConfig.writeXml( configurations, document );
1629 }
1630 }
1631 if ( !password.isEmpty() )
1632 {
1633 QString configurationsString;
1634 QTextStream ts( &configurationsString );
1635 configurations.save( ts, 2 );
1636 root.appendChild( document.createTextNode( QgsAuthCrypto::encrypt( password, civ, configurationsString ) ) );
1637 }
1638 else
1639 {
1640 root.appendChild( configurations );
1641 }
1642
1643 QFile file( filename );
1644 if ( !file.open( QFile::WriteOnly | QIODevice::Truncate ) )
1645 return false;
1646
1647 QTextStream ts( &file );
1648#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1649 ts.setCodec( "UTF-8" );
1650#endif
1651 document.save( ts, 2 );
1652 file.close();
1653 return true;
1654#else
1655 Q_UNUSED( filename )
1656 Q_UNUSED( authcfgs )
1657 Q_UNUSED( password )
1658 return false;
1659#endif
1660}
1661
1662bool QgsAuthManager::importAuthenticationConfigsFromXml( const QString &filename, const QString &password, bool overwrite )
1663{
1664#ifdef HAVE_AUTH
1666
1667 QFile file( filename );
1668 if ( !file.open( QFile::ReadOnly ) )
1669 {
1670 return false;
1671 }
1672
1673 QDomDocument document( u"qgis_authentication"_s );
1674 if ( !document.setContent( &file ) )
1675 {
1676 file.close();
1677 return false;
1678 }
1679 file.close();
1680
1681 QDomElement root = document.documentElement();
1682 if ( root.tagName() != "qgis_authentication"_L1 )
1683 {
1684 return false;
1685 }
1686
1687 QDomElement configurations;
1688 if ( root.hasAttribute( u"salt"_s ) )
1689 {
1690 QString salt = root.attribute( u"salt"_s );
1691 QString hash = root.attribute( u"hash"_s );
1692 QString civ = root.attribute( u"civ"_s );
1693 if ( !QgsAuthCrypto::verifyPasswordKeyHash( password, salt, hash ) )
1694 return false;
1695
1696 document.setContent( QgsAuthCrypto::decrypt( password, civ, root.text() ) );
1697 configurations = document.firstChild().toElement();
1698 }
1699 else
1700 {
1701 configurations = root.firstChildElement( u"configurations"_s );
1702 }
1703
1704 QDomElement configuration = configurations.firstChildElement();
1705 while ( !configuration.isNull() )
1706 {
1707 QgsAuthMethodConfig authMethodConfig;
1708 ( void )authMethodConfig.readXml( configuration );
1709 storeAuthenticationConfig( authMethodConfig, overwrite );
1710
1711 configuration = configuration.nextSiblingElement();
1712 }
1713 return true;
1714#else
1715 Q_UNUSED( filename )
1716 Q_UNUSED( password )
1717 Q_UNUSED( overwrite )
1718 return false;
1719#endif
1720}
1721
1723{
1724#ifdef HAVE_AUTH
1726
1727 QMutexLocker locker( mMutex.get() );
1728 if ( isDisabled() )
1729 return false;
1730
1731 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::DeleteConfiguration ) )
1732 {
1733 if ( defaultStorage->clearMethodConfigs() )
1734 {
1737 QgsDebugMsgLevel( u"REMOVED all configs from the default storage"_s, 2 );
1738 return true;
1739 }
1740 else
1741 {
1742 QgsDebugMsgLevel( u"FAILED to remove all configs from the default storage"_s, 2 );
1743 return false;
1744 }
1745 }
1746 else
1747 {
1748 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1749 return false;
1750 }
1751#else
1752 return false;
1753#endif
1754}
1755
1756
1758{
1759#ifdef HAVE_AUTH
1761
1762 QMutexLocker locker( mMutex.get() );
1763
1764 if ( sqliteDatabasePath().isEmpty() )
1765 {
1766 const char *err = QT_TR_NOOP( "The authentication storage is not filesystem-based" );
1767 QgsDebugError( err );
1769 return false;
1770 }
1771
1772 if ( !QFile::exists( sqliteDatabasePath() ) )
1773 {
1774 const char *err = QT_TR_NOOP( "No authentication database file found" );
1775 QgsDebugError( err );
1777 return false;
1778 }
1779
1780 // close any connection to current db
1782 QSqlDatabase authConn = authDatabaseConnection();
1784 if ( authConn.isValid() && authConn.isOpen() )
1785 authConn.close();
1786
1787 // duplicate current db file to 'qgis-auth_YYYY-MM-DD-HHMMSS.db' backup
1788 QString datestamp( QDateTime::currentDateTime().toString( u"yyyy-MM-dd-hhmmss"_s ) );
1789 QString dbbackup( sqliteDatabasePath() );
1790 dbbackup.replace( ".db"_L1, u"_%1.db"_s.arg( datestamp ) );
1791
1792 if ( !QFile::copy( sqliteDatabasePath(), dbbackup ) )
1793 {
1794 const char *err = QT_TR_NOOP( "Could not back up authentication database" );
1795 QgsDebugError( err );
1797 return false;
1798 }
1799
1800 if ( backuppath )
1801 *backuppath = dbbackup;
1802
1803 QgsDebugMsgLevel( u"Backed up auth database at %1"_s.arg( dbbackup ), 2 );
1804 return true;
1805#else
1806 Q_UNUSED( backuppath )
1807 return false;
1808#endif
1809}
1810
1811bool QgsAuthManager::eraseAuthenticationDatabase( bool backup, QString *backuppath )
1812{
1813#ifdef HAVE_AUTH
1815
1816 QMutexLocker locker( mMutex.get() );
1817 if ( isDisabled() )
1818 return false;
1819
1820 QString dbbackup;
1821 if ( backup && !backupAuthenticationDatabase( &dbbackup ) )
1822 {
1823 emit messageLog( tr( "Failed to backup authentication database" ), authManTag(), Qgis::MessageLevel::Warning );
1824 return false;
1825 }
1826
1827 if ( backuppath && !dbbackup.isEmpty() )
1828 *backuppath = dbbackup;
1829
1830 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ClearStorage ) )
1831 {
1832 if ( defaultStorage->erase() )
1833 {
1834 mMasterPass = QString();
1837 QgsDebugMsgLevel( u"ERASED all configs"_s, 2 );
1838 return true;
1839 }
1840 else
1841 {
1842 QgsDebugMsgLevel( u"FAILED to erase all configs"_s, 2 );
1843 return false;
1844 }
1845 }
1846 else
1847 {
1848 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1849 return false;
1850 }
1851
1852#ifndef QT_NO_SSL
1853 initSslCaches();
1854#endif
1855
1856 emit authDatabaseChanged();
1857
1858 return true;
1859#else
1860 Q_UNUSED( backup )
1861 Q_UNUSED( backuppath )
1862 return false;
1863#endif
1864}
1865
1866bool QgsAuthManager::updateNetworkRequest( QNetworkRequest &request, const QString &authcfg,
1867 const QString &dataprovider )
1868{
1869#ifdef HAVE_AUTH
1871
1872 if ( isDisabled() )
1873 return false;
1874
1875 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1876 if ( authmethod )
1877 {
1878 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkRequest ) )
1879 {
1880 QgsDebugError( u"Network request updating not supported by authcfg: %1"_s.arg( authcfg ) );
1881 return true;
1882 }
1883
1884 if ( !authmethod->updateNetworkRequest( request, authcfg, dataprovider.toLower() ) )
1885 {
1886 authmethod->clearCachedConfig( authcfg );
1887 return false;
1888 }
1889 return true;
1890 }
1891 return false;
1892#else
1893 Q_UNUSED( request )
1894 Q_UNUSED( authcfg )
1895 Q_UNUSED( dataprovider )
1896 return false;
1897#endif
1898}
1899
1900bool QgsAuthManager::updateNetworkReply( QNetworkReply *reply, const QString &authcfg,
1901 const QString &dataprovider )
1902{
1903#ifdef HAVE_AUTH
1905
1906 if ( isDisabled() )
1907 return false;
1908
1909 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1910 if ( authmethod )
1911 {
1912 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkReply ) )
1913 {
1914 QgsDebugMsgLevel( u"Network reply updating not supported by authcfg: %1"_s.arg( authcfg ), 3 );
1915 return true;
1916 }
1917
1918 if ( !authmethod->updateNetworkReply( reply, authcfg, dataprovider.toLower() ) )
1919 {
1920 authmethod->clearCachedConfig( authcfg );
1921 return false;
1922 }
1923 return true;
1924 }
1925
1926 return false;
1927#else
1928 Q_UNUSED( reply )
1929 Q_UNUSED( authcfg )
1930 Q_UNUSED( dataprovider )
1931 return false;
1932#endif
1933}
1934
1935bool QgsAuthManager::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
1936 const QString &dataprovider )
1937{
1938#ifdef HAVE_AUTH
1940
1941 if ( isDisabled() )
1942 return false;
1943
1944 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1945 if ( authmethod )
1946 {
1947 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::DataSourceUri ) )
1948 {
1949 QgsDebugError( u"Data source URI updating not supported by authcfg: %1"_s.arg( authcfg ) );
1950 return true;
1951 }
1952
1953 if ( !authmethod->updateDataSourceUriItems( connectionItems, authcfg, dataprovider.toLower() ) )
1954 {
1955 authmethod->clearCachedConfig( authcfg );
1956 return false;
1957 }
1958 return true;
1959 }
1960
1961 return false;
1962#else
1963 Q_UNUSED( connectionItems )
1964 Q_UNUSED( authcfg )
1965 Q_UNUSED( dataprovider )
1966 return false;
1967#endif
1968}
1969
1970bool QgsAuthManager::updateNetworkProxy( QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider )
1971{
1972#ifdef HAVE_AUTH
1974
1975 if ( isDisabled() )
1976 return false;
1977
1978 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1979 if ( authmethod )
1980 {
1981 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkProxy ) )
1982 {
1983 QgsDebugError( u"Proxy updating not supported by authcfg: %1"_s.arg( authcfg ) );
1984 return true;
1985 }
1986
1987 if ( !authmethod->updateNetworkProxy( proxy, authcfg, dataprovider.toLower() ) )
1988 {
1989 authmethod->clearCachedConfig( authcfg );
1990 return false;
1991 }
1992 QgsDebugMsgLevel( u"Proxy updated successfully from authcfg: %1"_s.arg( authcfg ), 2 );
1993 return true;
1994 }
1995
1996 return false;
1997#else
1998 Q_UNUSED( proxy )
1999 Q_UNUSED( authcfg )
2000 Q_UNUSED( dataprovider )
2001 return false;
2002#endif
2003}
2004
2005bool QgsAuthManager::storeAuthSetting( const QString &key, const QVariant &value, bool encrypt )
2006{
2007#ifdef HAVE_AUTH
2009
2010 QMutexLocker locker( mMutex.get() );
2011 if ( key.isEmpty() )
2012 return false;
2013
2014 QString storeval( value.toString() );
2015 if ( encrypt )
2016 {
2017 if ( !setMasterPassword( true ) )
2018 {
2019 return false;
2020 }
2021 else
2022 {
2023 storeval = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), value.toString() );
2024 }
2025 }
2026
2027 if ( existsAuthSetting( key ) && ! removeAuthSetting( key ) )
2028 {
2029 emit messageLog( tr( "Store setting: FAILED to remove pre-existing setting %1" ).arg( key ), authManTag(), Qgis::MessageLevel::Warning );
2030 return false;
2031 }
2032
2033 // Set the setting in the first storage that has the capability to store it
2034
2035 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateSetting ) )
2036 {
2037 if ( !defaultStorage->storeAuthSetting( key, storeval ) )
2038 {
2039 emit messageLog( tr( "Store setting: FAILED to store setting in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
2040 return false;
2041 }
2042 return true;
2043 }
2044 else
2045 {
2046 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2047 return false;
2048 }
2049#else
2050 Q_UNUSED( key )
2051 Q_UNUSED( value )
2052 Q_UNUSED( encrypt )
2053 return false;
2054#endif
2055}
2056
2057QVariant QgsAuthManager::authSetting( const QString &key, const QVariant &defaultValue, bool decrypt )
2058{
2059#ifdef HAVE_AUTH
2061
2062 QMutexLocker locker( mMutex.get() );
2063 if ( key.isEmpty() )
2064 return QVariant();
2065
2066 if ( decrypt && !setMasterPassword( true ) )
2067 return QVariant();
2068
2069 QVariant value = defaultValue;
2070
2071 // Loop through all storages with capability ReadSetting and get the setting from the first one that has the setting
2073
2074 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2075 {
2076 QString storeval = storage->loadAuthSetting( key );
2077 if ( !storeval.isEmpty() )
2078 {
2079 if ( decrypt )
2080 {
2081 storeval = QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), storeval );
2082 }
2083 value = storeval;
2084 break;
2085 }
2086 }
2087
2088 if ( storages.empty() )
2089 {
2090 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2091 }
2092
2093 return value;
2094#else
2095 Q_UNUSED( key )
2096 Q_UNUSED( defaultValue )
2097 Q_UNUSED( decrypt )
2098 return QVariant();
2099#endif
2100}
2101
2102bool QgsAuthManager::existsAuthSetting( const QString &key )
2103{
2104#ifdef HAVE_AUTH
2106
2107 QMutexLocker locker( mMutex.get() );
2108 if ( key.isEmpty() )
2109 return false;
2110
2111 // Loop through all storages with capability ReadSetting and get the setting from the first one that has the setting
2113
2114 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2115 {
2116
2117 if ( storage->authSettingExists( key ) )
2118 { return true; }
2119
2120 }
2121
2122 if ( storages.empty() )
2123 {
2124 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2125 }
2126
2127 return false;
2128#else
2129 Q_UNUSED( key )
2130 return false;
2131#endif
2132}
2133
2134bool QgsAuthManager::removeAuthSetting( const QString &key )
2135{
2136#ifdef HAVE_AUTH
2138
2139 QMutexLocker locker( mMutex.get() );
2140 if ( key.isEmpty() )
2141 return false;
2142
2143 // Loop through all storages with capability ReadSetting and delete from the first one that has the setting, fail if it has no capability
2145
2146 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2147 {
2148 if ( storage->authSettingExists( key ) )
2149 {
2151 {
2152 if ( !storage->removeAuthSetting( key ) )
2153 {
2154 emit messageLog( tr( "Remove setting: FAILED to remove setting from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2155 return false;
2156 }
2157 return true;
2158 }
2159 else
2160 {
2161 emit messageLog( tr( "Remove setting: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
2162 return false;
2163 }
2164 }
2165 }
2166
2167 if ( storages.empty() )
2168 {
2169 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2170 }
2171 return false;
2172#else
2173 Q_UNUSED( key )
2174 return false;
2175#endif
2176}
2177
2178#ifndef QT_NO_SSL
2179
2181
2183{
2184#ifdef HAVE_AUTH
2185 QgsScopedRuntimeProfile profile( "Initialize SSL cache" );
2186
2187 QMutexLocker locker( mMutex.get() );
2188 bool res = true;
2189 res = res && rebuildCaCertsCache();
2190 res = res && rebuildCertTrustCache();
2191 res = res && rebuildTrustedCaCertsCache();
2192 res = res && rebuildIgnoredSslErrorCache();
2193 mCustomConfigByHostCache.clear();
2194 mHasCheckedIfCustomConfigByHostExists = false;
2195
2196 if ( !res )
2197 QgsDebugError( u"Init of SSL caches FAILED"_s );
2198 return res;
2199#else
2200 return false;
2201#endif
2202}
2203
2204bool QgsAuthManager::storeCertIdentity( const QSslCertificate &cert, const QSslKey &key )
2205{
2206#ifdef HAVE_AUTH
2208
2209 QMutexLocker locker( mMutex.get() );
2210 if ( cert.isNull() )
2211 {
2212 QgsDebugError( u"Passed certificate is null"_s );
2213 return false;
2214 }
2215 if ( key.isNull() )
2216 {
2217 QgsDebugError( u"Passed private key is null"_s );
2218 return false;
2219 }
2220
2221 if ( !setMasterPassword( true ) )
2222 return false;
2223
2224 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2225
2226
2227 if ( existsCertIdentity( id ) && ! removeCertIdentity( id ) )
2228 {
2229 QgsDebugError( u"Store certificate identity: FAILED to remove pre-existing certificate identity %1"_s.arg( id ) );
2230 return false;
2231 }
2232
2233 QString keypem( QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), key.toPem() ) );
2234
2236 {
2237 if ( !defaultStorage->storeCertIdentity( cert, keypem ) )
2238 {
2239 emit messageLog( tr( "Store certificate identity: FAILED to store certificate identity in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
2240 return false;
2241 }
2242 return true;
2243 }
2244 else
2245 {
2246 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2247 return false;
2248 }
2249#else
2250 Q_UNUSED( cert )
2251 Q_UNUSED( key )
2252 return false;
2253#endif
2254}
2255
2256const QSslCertificate QgsAuthManager::certIdentity( const QString &id )
2257{
2258#ifdef HAVE_AUTH
2260
2261 QMutexLocker locker( mMutex.get() );
2262
2263 QSslCertificate cert;
2264
2265 if ( id.isEmpty() )
2266 return cert;
2267
2268 // Loop through all storages with capability ReadCertificateIdentity and get the certificate from the first one that has the certificate
2270
2271 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2272 {
2273 cert = storage->loadCertIdentity( id );
2274 if ( !cert.isNull() )
2275 {
2276 return cert;
2277 }
2278 }
2279
2280 if ( storages.empty() )
2281 {
2282 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2283 }
2284
2285 return cert;
2286#else
2287 Q_UNUSED( id )
2288 return QSslCertificate();
2289#endif
2290}
2291
2292const QPair<QSslCertificate, QSslKey> QgsAuthManager::certIdentityBundle( const QString &id )
2293{
2295
2296 QMutexLocker locker( mMutex.get() );
2297 QPair<QSslCertificate, QSslKey> bundle;
2298 if ( id.isEmpty() )
2299 return bundle;
2300
2301 if ( !setMasterPassword( true ) )
2302 return bundle;
2303
2304 // Loop through all storages with capability ReadCertificateIdentity and get the certificate from the first one that has the certificate
2306
2307 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2308 {
2309 if ( storage->certIdentityExists( id ) )
2310 {
2311 QPair<QSslCertificate, QString> encryptedBundle { storage->loadCertIdentityBundle( id ) };
2312 if ( encryptedBundle.first.isNull() )
2313 {
2314 QgsDebugError( u"Certificate identity bundle is null for id: %1"_s.arg( id ) );
2315 return bundle;
2316 }
2317 QSslKey key( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), encryptedBundle.second ).toLatin1(),
2318 QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey );
2319 if ( key.isNull() )
2320 {
2321 QgsDebugError( u"Certificate identity bundle: FAILED to create private key"_s );
2322 return bundle;
2323 }
2324 bundle = qMakePair( encryptedBundle.first, key );
2325 break;
2326 }
2327 }
2328
2329 if ( storages.empty() )
2330 {
2331 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2332 return bundle;
2333 }
2334
2335 return bundle;
2336}
2337
2338const QStringList QgsAuthManager::certIdentityBundleToPem( const QString &id )
2339{
2340#ifdef HAVE_AUTH
2342
2343 QMutexLocker locker( mMutex.get() );
2344 QPair<QSslCertificate, QSslKey> bundle( certIdentityBundle( id ) );
2345 if ( QgsAuthCertUtils::certIsViable( bundle.first ) && !bundle.second.isNull() )
2346 {
2347 return QStringList() << QString( bundle.first.toPem() ) << QString( bundle.second.toPem() );
2348 }
2349 return QStringList();
2350#else
2351 Q_UNUSED( id )
2352 return QStringList();
2353#endif
2354}
2355
2356const QList<QSslCertificate> QgsAuthManager::certIdentities()
2357{
2358#ifdef HAVE_AUTH
2360
2361 QMutexLocker locker( mMutex.get() );
2362 QList<QSslCertificate> certs;
2363
2364 // Loop through all storages with capability ReadCertificateIdentity and collect the certificates from all storages
2366
2367 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2368 {
2369 const QList<QSslCertificate> storageCerts = storage->certIdentities();
2370 // Add if not already in the list, warn otherwise
2371 for ( const QSslCertificate &cert : std::as_const( storageCerts ) )
2372 {
2373 if ( !certs.contains( cert ) )
2374 {
2375 certs.append( cert );
2376 }
2377 else
2378 {
2379 emit messageLog( tr( "Certificate already in the list: %1" ).arg( cert.issuerDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
2380 }
2381 }
2382 }
2383
2384 if ( storages.empty() )
2385 {
2386 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2387 }
2388
2389 return certs;
2390#else
2391 return QList<QSslCertificate>();
2392#endif
2393}
2394
2396{
2397#ifdef HAVE_AUTH
2399
2400 QMutexLocker locker( mMutex.get() );
2401
2402 if ( isDisabled() )
2403 return {};
2404
2405 // Loop through all storages with capability ReadCertificateIdentity and collect the certificate ids from all storages
2407
2408 QStringList ids;
2409
2410 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2411 {
2412 const QStringList storageIds = storage->certIdentityIds();
2413 // Add if not already in the list, warn otherwise
2414 for ( const QString &id : std::as_const( storageIds ) )
2415 {
2416 if ( !ids.contains( id ) )
2417 {
2418 ids.append( id );
2419 }
2420 else
2421 {
2422 emit messageLog( tr( "Certificate identity id already in the list: %1" ).arg( id ), authManTag(), Qgis::MessageLevel::Warning );
2423 }
2424 }
2425 }
2426
2427 return ids;
2428#else
2429 return QStringList();
2430#endif
2431}
2432
2433bool QgsAuthManager::existsCertIdentity( const QString &id )
2434{
2435#ifdef HAVE_AUTH
2437
2438 QMutexLocker locker( mMutex.get() );
2439 if ( id.isEmpty() )
2440 return false;
2441
2442 // Loop through all storages with capability ReadCertificateIdentity and check if the certificate exists in any storage
2444
2445 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2446 {
2447 if ( storage->certIdentityExists( id ) )
2448 {
2449 return true;
2450 }
2451 }
2452
2453 if ( storages.empty() )
2454 {
2455 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2456 }
2457
2458 return false;
2459#else
2460 Q_UNUSED( id )
2461 return false;
2462#endif
2463}
2464
2465bool QgsAuthManager::removeCertIdentity( const QString &id )
2466{
2467#ifdef HAVE_AUTH
2469
2470 QMutexLocker locker( mMutex.get() );
2471 if ( id.isEmpty() )
2472 {
2473 QgsDebugError( u"Passed bundle ID is empty"_s );
2474 return false;
2475 }
2476
2477 // Loop through all storages with capability ReadCertificateIdentity and delete from the first one that has the bundle, fail if it has no capability
2479
2480 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2481 {
2482 if ( storage->certIdentityExists( id ) )
2483 {
2484 if ( !storage->removeCertIdentity( id ) )
2485 {
2486 emit messageLog( tr( "Remove certificate identity: FAILED to remove certificate identity from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2487 return false;
2488 }
2489 return true;
2490 }
2491 }
2492
2493 if ( storages.empty() )
2494 {
2495 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2496 }
2497
2498 return false;
2499
2500#else
2501 Q_UNUSED( id )
2502 return false;
2503#endif
2504}
2505
2507{
2508#ifdef HAVE_AUTH
2510
2511 QMutexLocker locker( mMutex.get() );
2512 if ( config.isNull() )
2513 {
2514 QgsDebugError( u"Passed config is null"_s );
2515 return false;
2516 }
2517
2518 const QSslCertificate cert( config.sslCertificate() );
2519 const QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2520
2521 if ( existsSslCertCustomConfig( id, config.sslHostPort() ) && !removeSslCertCustomConfig( id, config.sslHostPort() ) )
2522 {
2523 QgsDebugError( u"Store SSL certificate custom config: FAILED to remove pre-existing config %1"_s.arg( id ) );
2524 return false;
2525 }
2526
2528 {
2529 if ( !defaultStorage->storeSslCertCustomConfig( config ) )
2530 {
2531 emit messageLog( tr( "Store SSL certificate custom config: FAILED to store config in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
2532 return false;
2533 }
2534 }
2535 else
2536 {
2537 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2538 return false;
2539 }
2540
2542 mCustomConfigByHostCache.clear();
2543
2544 return true;
2545#else
2546 Q_UNUSED( config )
2547 return false;
2548#endif
2549}
2550
2551const QgsAuthConfigSslServer QgsAuthManager::sslCertCustomConfig( const QString &id, const QString &hostport )
2552{
2553#ifdef HAVE_AUTH
2555
2556 QMutexLocker locker( mMutex.get() );
2558
2559 if ( id.isEmpty() || hostport.isEmpty() )
2560 {
2561 QgsDebugError( u"Passed config ID or host:port is empty"_s );
2562 return config;
2563 }
2564
2565 // Loop through all storages with capability ReadSslCertificateCustomConfig and get the config from the first one that has the config
2567
2568 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2569 {
2570 if ( storage->sslCertCustomConfigExists( id, hostport ) )
2571 {
2572 config = storage->loadSslCertCustomConfig( id, hostport );
2573 if ( !config.isNull() )
2574 {
2575 return config;
2576 }
2577 else
2578 {
2579 emit messageLog( tr( "Could not load SSL custom config %1 %2 from the storage." ).arg( id, hostport ), authManTag(), Qgis::MessageLevel::Critical );
2580 return config;
2581 }
2582 }
2583 }
2584
2585 if ( storages.empty() )
2586 {
2587 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2588 }
2589
2590 return config;
2591
2592#else
2593 Q_UNUSED( id )
2594 Q_UNUSED( hostport )
2595 return QgsAuthConfigSslServer();
2596#endif
2597}
2598
2600{
2601#ifdef HAVE_AUTH
2603
2605 if ( hostport.isEmpty() )
2606 {
2607 return config;
2608 }
2609
2610 QMutexLocker locker( mMutex.get() );
2611
2612 if ( mCustomConfigByHostCache.contains( hostport ) )
2613 return mCustomConfigByHostCache.value( hostport );
2614
2615 // Loop through all storages with capability ReadSslCertificateCustomConfig and get the config from the first one that has the config
2617
2618 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2619 {
2620 config = storage->loadSslCertCustomConfigByHost( hostport );
2621 if ( !config.isNull() )
2622 {
2623 mCustomConfigByHostCache.insert( hostport, config );
2624 }
2625
2626 }
2627
2628 if ( storages.empty() )
2629 {
2630 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2631 }
2632
2633 return config;
2634#else
2635 Q_UNUSED( hostport )
2636 return QgsAuthConfigSslServer();
2637#endif
2638}
2639
2640const QList<QgsAuthConfigSslServer> QgsAuthManager::sslCertCustomConfigs()
2641{
2642#ifdef HAVE_AUTH
2644
2645 QMutexLocker locker( mMutex.get() );
2646 QList<QgsAuthConfigSslServer> configs;
2647
2648 // Loop through all storages with capability ReadSslCertificateCustomConfig
2650
2651 QStringList ids;
2652
2653 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2654 {
2655 const QList<QgsAuthConfigSslServer> storageConfigs = storage->sslCertCustomConfigs();
2656 // Check if id + hostPort is not already in the list, warn otherwise
2657 for ( const auto &config : std::as_const( storageConfigs ) )
2658 {
2659 const QString id( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ) );
2660 const QString hostPort = config.sslHostPort();
2661 const QString shaHostPort( u"%1:%2"_s.arg( id, hostPort ) );
2662 if ( ! ids.contains( shaHostPort ) )
2663 {
2664 ids.append( shaHostPort );
2665 configs.append( config );
2666 }
2667 else
2668 {
2669 emit messageLog( tr( "SSL custom config already in the list: %1" ).arg( hostPort ), authManTag(), Qgis::MessageLevel::Warning );
2670 }
2671 }
2672 }
2673
2674 if ( storages.empty() )
2675 {
2676 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2677 }
2678
2679 return configs;
2680#else
2681 return QList<QgsAuthConfigSslServer>();
2682#endif
2683}
2684
2685bool QgsAuthManager::existsSslCertCustomConfig( const QString &id, const QString &hostPort )
2686{
2687#ifdef HAVE_AUTH
2689
2690 QMutexLocker locker( mMutex.get() );
2691 if ( id.isEmpty() || hostPort.isEmpty() )
2692 {
2693 QgsDebugError( u"Passed config ID or host:port is empty"_s );
2694 return false;
2695 }
2696
2697 // Loop through all storages with capability ReadSslCertificateCustomConfig
2699
2700 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2701 {
2702 if ( storage->sslCertCustomConfigExists( id, hostPort ) )
2703 {
2704 return true;
2705 }
2706 }
2707
2708 if ( storages.empty() )
2709 {
2710 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2711 }
2712
2713 return false;
2714#else
2715 Q_UNUSED( id )
2716 Q_UNUSED( hostPort )
2717 return false;
2718#endif
2719}
2720
2721bool QgsAuthManager::removeSslCertCustomConfig( const QString &id, const QString &hostport )
2722{
2723#ifdef HAVE_AUTH
2725
2726 QMutexLocker locker( mMutex.get() );
2727 if ( id.isEmpty() || hostport.isEmpty() )
2728 {
2729 QgsDebugError( u"Passed config ID or host:port is empty"_s );
2730 return false;
2731 }
2732
2733 mCustomConfigByHostCache.clear();
2734
2735 // Loop through all storages with capability DeleteSslCertificateCustomConfig
2737
2738 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2739 {
2740 if ( storage->sslCertCustomConfigExists( id, hostport ) )
2741 {
2742 if ( !storage->removeSslCertCustomConfig( id, hostport ) )
2743 {
2744 emit messageLog( tr( "FAILED to remove SSL cert custom config for host:port, id: %1, %2: %3" ).arg( hostport, id, storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2745 return false;
2746 }
2747 const QString shaHostPort( u"%1:%2"_s.arg( id, hostport ) );
2748 if ( mIgnoredSslErrorsCache.contains( shaHostPort ) )
2749 {
2750 mIgnoredSslErrorsCache.remove( shaHostPort );
2751 }
2752 return true;
2753 }
2754 }
2755
2756 if ( storages.empty() )
2757 {
2758 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2759 }
2760
2761 return false;
2762#else
2763 Q_UNUSED( id )
2764 Q_UNUSED( hostport )
2765 return false;
2766#endif
2767}
2768
2769
2771{
2772#ifdef HAVE_AUTH
2774
2775 QMutexLocker locker( mMutex.get() );
2776 if ( !mIgnoredSslErrorsCache.isEmpty() )
2777 {
2778 QgsDebugMsgLevel( u"Ignored SSL errors cache items:"_s, 1 );
2779 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = mIgnoredSslErrorsCache.constBegin();
2780 while ( i != mIgnoredSslErrorsCache.constEnd() )
2781 {
2782 QStringList errs;
2783 for ( auto err : i.value() )
2784 {
2785 errs << QgsAuthCertUtils::sslErrorEnumString( err );
2786 }
2787 QgsDebugMsgLevel( u"%1 = %2"_s.arg( i.key(), errs.join( ", " ) ), 1 );
2788 ++i;
2789 }
2790 }
2791 else
2792 {
2793 QgsDebugMsgLevel( u"Ignored SSL errors cache EMPTY"_s, 2 );
2794 }
2795#endif
2796}
2797
2799{
2800#ifdef HAVE_AUTH
2802
2803 QMutexLocker locker( mMutex.get() );
2804 if ( config.isNull() )
2805 {
2806 QgsDebugError( u"Passed config is null"_s );
2807 return false;
2808 }
2809
2810 QString shahostport( u"%1:%2"_s
2811 .arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ).trimmed(),
2812 config.sslHostPort().trimmed() ) );
2813 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2814 {
2815 mIgnoredSslErrorsCache.remove( shahostport );
2816 }
2817 const QList<QSslError::SslError> errenums( config.sslIgnoredErrorEnums() );
2818 if ( !errenums.isEmpty() )
2819 {
2820 mIgnoredSslErrorsCache.insert( shahostport, QSet<QSslError::SslError>( errenums.begin(), errenums.end() ) );
2821 QgsDebugMsgLevel( u"Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1"_s.arg( shahostport ), 2 );
2823 return true;
2824 }
2825
2826 QgsDebugMsgLevel( u"No ignored SSL errors to cache for sha:host:port = %1"_s.arg( shahostport ), 2 );
2827 return true;
2828#else
2829 Q_UNUSED( config )
2830 return false;
2831#endif
2832}
2833
2834bool QgsAuthManager::updateIgnoredSslErrorsCache( const QString &shahostport, const QList<QSslError> &errors )
2835{
2836#ifdef HAVE_AUTH
2838
2839 QMutexLocker locker( mMutex.get() );
2840 const thread_local QRegularExpression rx( QRegularExpression::anchoredPattern( "\\S+:\\S+:\\d+" ) );
2841 if ( !rx.match( shahostport ).hasMatch() )
2842 {
2843 QgsDebugError( "Passed shahostport does not match \\S+:\\S+:\\d+, "
2844 "e.g. 74a4ef5ea94512a43769b744cda0ca5049a72491:www.example.com:443" );
2845 return false;
2846 }
2847
2848 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2849 {
2850 mIgnoredSslErrorsCache.remove( shahostport );
2851 }
2852
2853 if ( errors.isEmpty() )
2854 {
2855 QgsDebugError( u"Passed errors list empty"_s );
2856 return false;
2857 }
2858
2859 QSet<QSslError::SslError> errs;
2860 for ( const auto &error : errors )
2861 {
2862 if ( error.error() == QSslError::NoError )
2863 continue;
2864
2865 errs.insert( error.error() );
2866 }
2867
2868 if ( errs.isEmpty() )
2869 {
2870 QgsDebugError( u"Passed errors list does not contain errors"_s );
2871 return false;
2872 }
2873
2874 mIgnoredSslErrorsCache.insert( shahostport, errs );
2875
2876 QgsDebugMsgLevel( u"Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1"_s.arg( shahostport ), 2 );
2878 return true;
2879#else
2880 Q_UNUSED( shahostport )
2881 Q_UNUSED( errors )
2882 return false;
2883#endif
2884}
2885
2887{
2888#ifdef HAVE_AUTH
2890
2891 QMutexLocker locker( mMutex.get() );
2892 QHash<QString, QSet<QSslError::SslError> > prevcache( mIgnoredSslErrorsCache );
2893 QHash<QString, QSet<QSslError::SslError> > nextcache;
2894
2895 // Loop through all storages with capability ReadSslCertificateCustomConfig
2897
2898 QStringList ids;
2899
2900 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2901 {
2902 const auto customConfigs { storage->sslCertCustomConfigs() };
2903 for ( const auto &config : std::as_const( customConfigs ) )
2904 {
2905 const QString shaHostPort( u"%1:%2"_s.arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ), config.sslHostPort() ) );
2906 if ( ! ids.contains( shaHostPort ) )
2907 {
2908 ids.append( shaHostPort );
2909 if ( !config.sslIgnoredErrorEnums().isEmpty() )
2910 {
2911 nextcache.insert( shaHostPort, QSet<QSslError::SslError>( config.sslIgnoredErrorEnums().cbegin(), config.sslIgnoredErrorEnums().cend() ) );
2912 }
2913 if ( prevcache.contains( shaHostPort ) )
2914 {
2915 prevcache.remove( shaHostPort );
2916 }
2917 }
2918 else
2919 {
2920 emit messageLog( tr( "SSL custom config already in the list: %1" ).arg( config.sslHostPort() ), authManTag(), Qgis::MessageLevel::Warning );
2921 }
2922 }
2923 }
2924
2925 if ( !prevcache.isEmpty() )
2926 {
2927 // preserve any existing per-session ignored errors for hosts
2928 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = prevcache.constBegin();
2929 while ( i != prevcache.constEnd() )
2930 {
2931 nextcache.insert( i.key(), i.value() );
2932 ++i;
2933 }
2934 }
2935
2936 if ( nextcache != mIgnoredSslErrorsCache )
2937 {
2938 mIgnoredSslErrorsCache.clear();
2939 mIgnoredSslErrorsCache = nextcache;
2940 QgsDebugMsgLevel( u"Rebuild of ignored SSL errors cache SUCCEEDED"_s, 2 );
2942 return true;
2943 }
2944
2945 QgsDebugMsgLevel( u"Rebuild of ignored SSL errors cache SAME AS BEFORE"_s, 2 );
2947 return true;
2948#else
2949 return false;
2950#endif
2951}
2952
2953bool QgsAuthManager::storeCertAuthorities( const QList<QSslCertificate> &certs )
2954{
2955#ifdef HAVE_AUTH
2957
2958 QMutexLocker locker( mMutex.get() );
2959 if ( certs.isEmpty() )
2960 {
2961 QgsDebugError( u"Passed certificate list has no certs"_s );
2962 return false;
2963 }
2964
2965 for ( const auto &cert : certs )
2966 {
2967 if ( !storeCertAuthority( cert ) )
2968 return false;
2969 }
2970 return true;
2971#else
2972 Q_UNUSED( certs )
2973 return false;
2974#endif
2975}
2976
2977bool QgsAuthManager::storeCertAuthority( const QSslCertificate &cert )
2978{
2979#ifdef HAVE_AUTH
2981
2982 QMutexLocker locker( mMutex.get() );
2983 // don't refuse !cert.isValid() (actually just expired) CAs,
2984 // as user may want to ignore that SSL connection error
2985 if ( cert.isNull() )
2986 {
2987 QgsDebugError( u"Passed certificate is null"_s );
2988 return false;
2989 }
2990
2991 if ( existsCertAuthority( cert ) && !removeCertAuthority( cert ) )
2992 {
2993 QgsDebugError( u"Store certificate authority: FAILED to remove pre-existing certificate authority"_s );
2994 return false;
2995 }
2996
2998 {
2999 return defaultStorage->storeCertAuthority( cert );
3000 }
3001 else
3002 {
3003 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3004 return false;
3005 }
3006
3007 return false;
3008#else
3009 Q_UNUSED( cert )
3010 return false;
3011#endif
3012}
3013
3014const QSslCertificate QgsAuthManager::certAuthority( const QString &id )
3015{
3016#ifdef HAVE_AUTH
3018
3019 QMutexLocker locker( mMutex.get() );
3020 QSslCertificate emptycert;
3021 QSslCertificate cert;
3022 if ( id.isEmpty() )
3023 return emptycert;
3024
3025 // Loop through all storages with capability ReadCertificateAuthority and get the certificate from the first one that has the certificate
3027
3028 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3029 {
3030 cert = storage->loadCertAuthority( id );
3031 if ( !cert.isNull() )
3032 {
3033 return cert;
3034 }
3035 }
3036
3037 if ( storages.empty() )
3038 {
3039 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
3040 return emptycert;
3041 }
3042
3043 return cert;
3044#else
3045 Q_UNUSED( id )
3046 return QSslCertificate();
3047#endif
3048}
3049
3050bool QgsAuthManager::existsCertAuthority( const QSslCertificate &cert )
3051{
3052#ifdef HAVE_AUTH
3054
3055 QMutexLocker locker( mMutex.get() );
3056 if ( cert.isNull() )
3057 {
3058 QgsDebugError( u"Passed certificate is null"_s );
3059 return false;
3060 }
3061
3062 // Loop through all storages with capability ReadCertificateAuthority and get the certificate from the first one that has the certificate
3064
3065 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3066 {
3067 if ( storage->certAuthorityExists( cert ) )
3068 {
3069 return true;
3070 }
3071 }
3072
3073 if ( storages.empty() )
3074 {
3075 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
3076 }
3077
3078 return false;
3079#else
3080 return false;
3081#endif
3082}
3083
3084bool QgsAuthManager::removeCertAuthority( const QSslCertificate &cert )
3085{
3086#ifdef HAVE_AUTH
3088
3089 QMutexLocker locker( mMutex.get() );
3090 if ( cert.isNull() )
3091 {
3092 QgsDebugError( u"Passed certificate is null"_s );
3093 return false;
3094 }
3095
3096 // Loop through all storages with capability ReadCertificateAuthority and delete from the first one that has the certificate, fail if it has no capability
3098
3099 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3100 {
3101 if ( storage->certAuthorityExists( cert ) )
3102 {
3103
3105 {
3106 emit messageLog( tr( "Remove certificate: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
3107 return false;
3108 }
3109
3110 if ( !storage->removeCertAuthority( cert ) )
3111 {
3112 emit messageLog( tr( "Remove certificate authority: FAILED to remove certificate authority from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
3113 return false;
3114 }
3115 return true;
3116 }
3117 }
3118
3119 if ( storages.empty() )
3120 {
3121 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3122 }
3123
3124 return false;
3125#else
3126 Q_UNUSED( cert )
3127 return false;
3128#endif
3129}
3130
3131const QList<QSslCertificate> QgsAuthManager::systemRootCAs()
3132{
3133#ifdef HAVE_AUTH
3134 return QSslConfiguration::systemCaCertificates();
3135#else
3136 return QList<QSslCertificate>();
3137#endif
3138}
3139
3140const QList<QSslCertificate> QgsAuthManager::extraFileCAs()
3141{
3142#ifdef HAVE_AUTH
3144
3145 QMutexLocker locker( mMutex.get() );
3146 QList<QSslCertificate> certs;
3147 QList<QSslCertificate> filecerts;
3148 QVariant cafileval = QgsAuthManager::instance()->authSetting( u"cafile"_s );
3149 if ( QgsVariantUtils::isNull( cafileval ) )
3150 return certs;
3151
3152 QVariant allowinvalid = QgsAuthManager::instance()->authSetting( u"cafileallowinvalid"_s, QVariant( false ) );
3153 if ( QgsVariantUtils::isNull( allowinvalid ) )
3154 return certs;
3155
3156 QString cafile( cafileval.toString() );
3157 if ( !cafile.isEmpty() && QFile::exists( cafile ) )
3158 {
3159 filecerts = QgsAuthCertUtils::certsFromFile( cafile );
3160 }
3161 // only CAs or certs capable of signing other certs are allowed
3162 for ( const auto &cert : std::as_const( filecerts ) )
3163 {
3164 if ( !allowinvalid.toBool() && ( cert.isBlacklisted()
3165 || cert.isNull()
3166 || cert.expiryDate() <= QDateTime::currentDateTime()
3167 || cert.effectiveDate() > QDateTime::currentDateTime() ) )
3168 {
3169 continue;
3170 }
3171
3172 if ( QgsAuthCertUtils::certificateIsAuthorityOrIssuer( cert ) )
3173 {
3174 certs << cert;
3175 }
3176 }
3177 return certs;
3178#else
3179 return QList<QSslCertificate>();
3180#endif
3181}
3182
3183const QList<QSslCertificate> QgsAuthManager::databaseCAs()
3184{
3185#ifdef HAVE_AUTH
3187
3188 QMutexLocker locker( mMutex.get() );
3189
3190 // Loop through all storages with capability ReadCertificateAuthority and collect the certificates from all storages
3192
3193 QList<QSslCertificate> certs;
3194
3195 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3196 {
3197 const QList<QSslCertificate> storageCerts = storage->caCerts();
3198 // Add if not already in the list, warn otherwise
3199 for ( const QSslCertificate &cert : std::as_const( storageCerts ) )
3200 {
3201 if ( !certs.contains( cert ) )
3202 {
3203 certs.append( cert );
3204 }
3205 else
3206 {
3207 emit messageLog( tr( "Certificate already in the list: %1" ).arg( cert.issuerDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
3208 }
3209 }
3210 }
3211
3212 if ( storages.empty() )
3213 {
3214 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3215 }
3216
3217 return certs;
3218#else
3219 return QList<QSslCertificate>();
3220#endif
3221}
3222
3223const QMap<QString, QSslCertificate> QgsAuthManager::mappedDatabaseCAs()
3224{
3226
3227 QMutexLocker locker( mMutex.get() );
3228 return QgsAuthCertUtils::mapDigestToCerts( databaseCAs() );
3229}
3230
3232{
3233#ifdef HAVE_AUTH
3235
3236 QMutexLocker locker( mMutex.get() );
3237 mCaCertsCache.clear();
3238 // in reverse order of precedence, with regards to duplicates, so QMap inserts overwrite
3239 insertCaCertInCache( QgsAuthCertUtils::SystemRoot, systemRootCAs() );
3240 insertCaCertInCache( QgsAuthCertUtils::FromFile, extraFileCAs() );
3241 insertCaCertInCache( QgsAuthCertUtils::InDatabase, databaseCAs() );
3242
3243 bool res = !mCaCertsCache.isEmpty(); // should at least contain system root CAs
3244 if ( !res )
3245 QgsDebugError( u"Rebuild of CA certs cache FAILED"_s );
3246 return res;
3247#else
3248 return false;
3249#endif
3250}
3251
3253{
3254#ifdef HAVE_AUTH
3256
3257 QMutexLocker locker( mMutex.get() );
3258 if ( cert.isNull() )
3259 {
3260 QgsDebugError( u"Passed certificate is null."_s );
3261 return false;
3262 }
3263
3264 if ( certTrustPolicy( cert ) == policy )
3265 {
3266 return true;
3267 }
3268
3270 {
3271 emit messageLog( tr( "Could not delete pre-existing certificate trust policy." ), authManTag(), Qgis::MessageLevel::Warning );
3272 return false;
3273 }
3274
3276 {
3277 return defaultStorage->storeCertTrustPolicy( cert, policy );
3278 }
3279 else
3280 {
3281 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3282 return false;
3283 }
3284#else
3285 Q_UNUSED( cert )
3286 Q_UNUSED( policy )
3287 return false;
3288#endif
3289}
3290
3292{
3293#ifdef HAVE_AUTH
3295
3296 QMutexLocker locker( mMutex.get() );
3297 if ( cert.isNull() )
3298 {
3299 QgsDebugError( u"Passed certificate is null"_s );
3301 }
3302
3303 // Loop through all storages with capability ReadCertificateTrustPolicy and get the policy from the first one that has the policy
3305
3306 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3307 {
3309 if ( policy != QgsAuthCertUtils::DefaultTrust )
3310 {
3311 return policy;
3312 }
3313 }
3314
3315 if ( storages.empty() )
3316 {
3317 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
3318 }
3319
3321#else
3322 Q_UNUSED( cert )
3324#endif
3325}
3326
3327bool QgsAuthManager::removeCertTrustPolicies( const QList<QSslCertificate> &certs )
3328{
3329#ifdef HAVE_AUTH
3331
3332 QMutexLocker locker( mMutex.get() );
3333 if ( certs.empty() )
3334 {
3335 QgsDebugError( u"Passed certificate list has no certs"_s );
3336 return false;
3337 }
3338
3339 for ( const auto &cert : certs )
3340 {
3341 if ( !removeCertTrustPolicy( cert ) )
3342 return false;
3343 }
3344 return true;
3345#else
3346 Q_UNUSED( certs )
3347 return false;
3348#endif
3349}
3350
3351bool QgsAuthManager::removeCertTrustPolicy( const QSslCertificate &cert )
3352{
3353#ifdef HAVE_AUTH
3355
3356 QMutexLocker locker( mMutex.get() );
3357 if ( cert.isNull() )
3358 {
3359 QgsDebugError( u"Passed certificate is null"_s );
3360 return false;
3361 }
3362
3363 // Loop through all storages with capability ReadCertificateTrustPolicy and delete from the first one that has the policy, fail if it has no capability
3365
3366 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3367 {
3368 if ( storage->certTrustPolicyExists( cert ) )
3369 {
3371 {
3372 emit messageLog( tr( "Remove certificate trust policy: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
3373 return false;
3374 }
3375
3376 if ( !storage->removeCertTrustPolicy( cert ) )
3377 {
3378 emit messageLog( tr( "Remove certificate trust policy: FAILED to remove certificate trust policy from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
3379 return false;
3380 }
3381 return true;
3382 }
3383 }
3384
3385 if ( storages.empty() )
3386 {
3387 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3388 }
3389
3390 return false;
3391#else
3392 Q_UNUSED( cert )
3393 return false;
3394#endif
3395}
3396
3398{
3399#ifdef HAVE_AUTH
3401
3402 QMutexLocker locker( mMutex.get() );
3403 if ( cert.isNull() )
3404 {
3406 }
3407
3408 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
3409 const QStringList &trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
3410 const QStringList &untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
3411
3413 if ( trustedids.contains( id ) )
3414 {
3416 }
3417 else if ( untrustedids.contains( id ) )
3418 {
3420 }
3421 return policy;
3422#else
3423 Q_UNUSED( cert )
3425#endif
3426}
3427
3429{
3430#ifdef HAVE_AUTH
3432
3433 if ( policy == QgsAuthCertUtils::DefaultTrust )
3434 {
3435 // set default trust policy to Trusted by removing setting
3436 return removeAuthSetting( u"certdefaulttrust"_s );
3437 }
3438 return storeAuthSetting( u"certdefaulttrust"_s, static_cast< int >( policy ) );
3439#else
3440 Q_UNUSED( policy )
3441 return false;
3442#endif
3443}
3444
3446{
3447#ifdef HAVE_AUTH
3449
3450 QMutexLocker locker( mMutex.get() );
3451 QVariant policy( authSetting( u"certdefaulttrust"_s ) );
3452 if ( QgsVariantUtils::isNull( policy ) )
3453 {
3455 }
3456 return static_cast< QgsAuthCertUtils::CertTrustPolicy >( policy.toInt() );
3457#else
3459#endif
3460}
3461
3463{
3464#ifdef HAVE_AUTH
3466
3467 QMutexLocker locker( mMutex.get() );
3468 mCertTrustCache.clear();
3469
3470 // Loop through all storages with capability ReadCertificateTrustPolicy
3472
3473 QStringList ids;
3474
3475 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3476 {
3477
3478 const auto trustedCerts { storage->caCertsPolicy() };
3479 for ( auto it = trustedCerts.cbegin(); it != trustedCerts.cend(); ++it )
3480 {
3481 const QString id { it.key( )};
3482 if ( ! ids.contains( id ) )
3483 {
3484 ids.append( id );
3485 const QgsAuthCertUtils::CertTrustPolicy policy( it.value() );
3487 {
3488 QStringList ids;
3489 if ( mCertTrustCache.contains( QgsAuthCertUtils::Trusted ) )
3490 {
3491 ids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
3492 }
3493 mCertTrustCache.insert( QgsAuthCertUtils::Trusted, ids << it.key() );
3494 }
3495 }
3496 else
3497 {
3498 emit messageLog( tr( "Certificate already in the list: %1" ).arg( it.key() ), authManTag(), Qgis::MessageLevel::Warning );
3499 }
3500 }
3501 }
3502
3503 if ( ! storages.empty() )
3504 {
3505 QgsDebugMsgLevel( u"Rebuild of cert trust policy cache SUCCEEDED"_s, 2 );
3506 return true;
3507 }
3508 else
3509 {
3510 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3511 return false;
3512 }
3513#else
3514 return false;
3515#endif
3516}
3517
3518const QList<QSslCertificate> QgsAuthManager::trustedCaCerts( bool includeinvalid )
3519{
3520#ifdef HAVE_AUTH
3522
3523 QMutexLocker locker( mMutex.get() );
3525 QStringList trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
3526 QStringList untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
3527 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
3528
3529 QList<QSslCertificate> trustedcerts;
3530 for ( int i = 0; i < certpairs.size(); ++i )
3531 {
3532 QSslCertificate cert( certpairs.at( i ).second );
3533 QString certid( QgsAuthCertUtils::shaHexForCert( cert ) );
3534 if ( trustedids.contains( certid ) )
3535 {
3536 // trusted certs are always added regardless of their validity
3537 trustedcerts.append( cert );
3538 }
3539 else if ( defaultpolicy == QgsAuthCertUtils::Trusted && !untrustedids.contains( certid ) )
3540 {
3541 if ( !includeinvalid && !QgsAuthCertUtils::certIsViable( cert ) )
3542 continue;
3543 trustedcerts.append( cert );
3544 }
3545 }
3546
3547 // update application default SSL config for new requests
3548 QSslConfiguration sslconfig( QSslConfiguration::defaultConfiguration() );
3549 sslconfig.setCaCertificates( trustedcerts );
3550 QSslConfiguration::setDefaultConfiguration( sslconfig );
3551
3552 return trustedcerts;
3553#else
3554 Q_UNUSED( includeinvalid )
3555 return QList<QSslCertificate>();
3556#endif
3557}
3558
3559const QList<QSslCertificate> QgsAuthManager::untrustedCaCerts( QList<QSslCertificate> trustedCAs )
3560{
3561#ifdef HAVE_AUTH
3563
3564 QMutexLocker locker( mMutex.get() );
3565 if ( trustedCAs.isEmpty() )
3566 {
3567 if ( mTrustedCaCertsCache.isEmpty() )
3568 {
3570 }
3571 trustedCAs = trustedCaCertsCache();
3572 }
3573
3574 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
3575
3576 QList<QSslCertificate> untrustedCAs;
3577 for ( int i = 0; i < certpairs.size(); ++i )
3578 {
3579 QSslCertificate cert( certpairs.at( i ).second );
3580 if ( !trustedCAs.contains( cert ) )
3581 {
3582 untrustedCAs.append( cert );
3583 }
3584 }
3585 return untrustedCAs;
3586#else
3587 Q_UNUSED( trustedCAs )
3588 return QList<QSslCertificate>();
3589#endif
3590}
3591
3593{
3594#ifdef HAVE_AUTH
3596
3597 QMutexLocker locker( mMutex.get() );
3598 mTrustedCaCertsCache = trustedCaCerts();
3599 QgsDebugMsgLevel( u"Rebuilt trusted cert authorities cache"_s, 2 );
3600 // TODO: add some error trapping for the operation
3601 return true;
3602#else
3603 return false;
3604#endif
3605}
3606
3608{
3609#ifdef HAVE_AUTH
3611
3612 QMutexLocker locker( mMutex.get() );
3613 return QgsAuthCertUtils::certsToPemText( trustedCaCertsCache() );
3614#else
3615 return QByteArray();
3616#endif
3617}
3618
3620{
3621#ifdef HAVE_AUTH
3623
3624 QMutexLocker locker( mMutex.get() );
3625 if ( masterPasswordIsSet() )
3626 {
3627 return passwordHelperWrite( mMasterPass );
3628 }
3629 return false;
3630#else
3631 return false;
3632#endif
3633}
3634
3636{
3637#ifdef HAVE_AUTH
3638 if ( !passwordHelperEnabled() )
3639 return false;
3640
3641 bool readOk = false;
3642 const QString currentPass = passwordHelperRead( readOk );
3643 if ( !readOk )
3644 return false;
3645
3646 if ( !currentPass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) )
3647 {
3648 return verifyMasterPassword( currentPass );
3649 }
3650 return false;
3651#else
3652 return false;
3653#endif
3654}
3655
3657{
3658#ifdef HAVE_AUTH
3659#if defined(Q_OS_MAC)
3660 return titleCase ? QObject::tr( "Keychain" ) : QObject::tr( "keychain" );
3661#elif defined(Q_OS_WIN)
3662 return titleCase ? QObject::tr( "Password Manager" ) : QObject::tr( "password manager" );
3663#elif defined(Q_OS_LINUX)
3664
3665 const QString desktopSession = qgetenv( "DESKTOP_SESSION" );
3666 const QString currentDesktop = qgetenv( "XDG_CURRENT_DESKTOP" );
3667 const QString gdmSession = qgetenv( "GDMSESSION" );
3668 // lets use a more precise string if we're running on KDE!
3669 if ( desktopSession.contains( "kde"_L1, Qt::CaseInsensitive ) || currentDesktop.contains( "kde"_L1, Qt::CaseInsensitive ) || gdmSession.contains( "kde"_L1, Qt::CaseInsensitive ) )
3670 {
3671 return titleCase ? QObject::tr( "Wallet" ) : QObject::tr( "wallet" );
3672 }
3673
3674 return titleCase ? QObject::tr( "Wallet/Key Ring" ) : QObject::tr( "wallet/key ring" );
3675#else
3676 return titleCase ? QObject::tr( "Password Manager" ) : QObject::tr( "password manager" );
3677#endif
3678#else
3679 Q_UNUSED( titleCase )
3680 return QString();
3681#endif
3682}
3683
3684
3686
3687#endif
3688
3690{
3691#ifdef HAVE_AUTH
3693
3694 if ( isDisabled() )
3695 return;
3696
3697 const QStringList ids = configIds();
3698 for ( const auto &authcfg : ids )
3699 {
3700 clearCachedConfig( authcfg );
3701 }
3702#endif
3703}
3704
3705void QgsAuthManager::clearCachedConfig( const QString &authcfg )
3706{
3707#ifdef HAVE_AUTH
3709
3710 if ( isDisabled() )
3711 return;
3712
3713 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
3714 if ( authmethod )
3715 {
3716 authmethod->clearCachedConfig( authcfg );
3717 }
3718#else
3719 Q_UNUSED( authcfg )
3720#endif
3721}
3722
3723void QgsAuthManager::writeToConsole( const QString &message,
3724 const QString &tag,
3725 Qgis::MessageLevel level )
3726{
3727#ifdef HAVE_AUTH
3728 Q_UNUSED( tag )
3729
3731
3732 // only output WARNING and CRITICAL messages
3733 if ( level == Qgis::MessageLevel::Info )
3734 return;
3735
3736 QString msg;
3737 switch ( level )
3738 {
3740 msg += "WARNING: "_L1;
3741 break;
3743 msg += "ERROR: "_L1;
3744 break;
3745 default:
3746 break;
3747 }
3748 msg += message;
3749
3750 QTextStream out( stdout, QIODevice::WriteOnly );
3751 out << msg << Qt::endl;
3752#else
3753 Q_UNUSED( message )
3754 Q_UNUSED( tag )
3755 Q_UNUSED( level )
3756#endif
3757}
3758
3759void QgsAuthManager::tryToStartDbErase()
3760{
3761#ifdef HAVE_AUTH
3763
3764 ++mScheduledDbEraseRequestCount;
3765 // wait a total of 90 seconds for GUI availiability or user interaction, then cancel schedule
3766 int trycutoff = 90 / ( mScheduledDbEraseRequestWait ? mScheduledDbEraseRequestWait : 3 );
3767 if ( mScheduledDbEraseRequestCount >= trycutoff )
3768 {
3770 QgsDebugMsgLevel( u"authDatabaseEraseRequest emitting/scheduling canceled"_s, 2 );
3771 return;
3772 }
3773 else
3774 {
3775 QgsDebugMsgLevel( u"authDatabaseEraseRequest attempt (%1 of %2)"_s
3776 .arg( mScheduledDbEraseRequestCount ).arg( trycutoff ), 2 );
3777 }
3778
3779 if ( scheduledAuthDatabaseErase() && !mScheduledDbEraseRequestEmitted && mMutex->tryLock() )
3780 {
3781 // see note in header about this signal's use
3782 mScheduledDbEraseRequestEmitted = true;
3784
3785 mMutex->unlock();
3786
3787 QgsDebugMsgLevel( u"authDatabaseEraseRequest emitted"_s, 2 );
3788 return;
3789 }
3790 QgsDebugMsgLevel( u"authDatabaseEraseRequest emit skipped"_s, 2 );
3791#endif
3792}
3793
3794
3796{
3797#ifdef HAVE_AUTH
3798 QMutexLocker locker( mMutex.get() );
3799
3800 QMapIterator<QThread *, QMetaObject::Connection> iterator( mConnectedThreads );
3801 while ( iterator.hasNext() )
3802 {
3803 iterator.next();
3804 QThread::disconnect( iterator.value() );
3805 }
3806
3807 if ( !mAuthInit )
3808 return;
3809
3810 locker.unlock();
3811
3812 if ( !isDisabled() )
3813 {
3815 qDeleteAll( mAuthMethods );
3816
3818 QSqlDatabase authConn = authDatabaseConnection();
3820 if ( authConn.isValid() && authConn.isOpen() )
3821 authConn.close();
3822 }
3823
3824 QSqlDatabase::removeDatabase( u"authentication.configs"_s );
3825#endif
3826}
3827
3829{
3830 QMutexLocker locker( mMutex.get() );
3831 if ( ! mAuthConfigurationStorageRegistry )
3832 {
3833 mAuthConfigurationStorageRegistry = std::make_unique<QgsAuthConfigurationStorageRegistry>();
3834 }
3835 return mAuthConfigurationStorageRegistry.get();
3836}
3837
3838
3839QString QgsAuthManager::passwordHelperName() const
3840{
3841#ifdef HAVE_AUTH
3842 return tr( "Password Helper" );
3843#else
3844 return QString();
3845#endif
3846}
3847
3848
3849void QgsAuthManager::passwordHelperLog( const QString &msg ) const
3850{
3851#ifdef HAVE_AUTH
3853
3855 {
3856 QgsMessageLog::logMessage( msg, passwordHelperName() );
3857 }
3858#else
3859 Q_UNUSED( msg )
3860#endif
3861}
3862
3864{
3865#ifdef HAVE_AUTH
3867
3868 passwordHelperLog( tr( "Opening %1 for DELETE…" ).arg( passwordHelperDisplayName() ) );
3869 bool result;
3870 QKeychain::DeletePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3871 QgsSettings settings;
3872 job.setInsecureFallback( settings.value( u"password_helper_insecure_fallback"_s, false, QgsSettings::Section::Auth ).toBool() );
3873 job.setAutoDelete( false );
3874 job.setKey( authPasswordHelperKeyName() );
3875 QEventLoop loop;
3876 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3877 job.start();
3878 loop.exec();
3879 if ( job.error() )
3880 {
3881 mPasswordHelperErrorCode = job.error();
3882 mPasswordHelperErrorMessage = tr( "Delete password failed: %1." ).arg( job.errorString() );
3883 // Signals used in the tests to exit main application loop
3884 emit passwordHelperFailure();
3885 result = false;
3886 }
3887 else
3888 {
3889 // Signals used in the tests to exit main application loop
3890 emit passwordHelperSuccess();
3891 result = true;
3892 }
3893 passwordHelperProcessError();
3894 return result;
3895#else
3896 return false;
3897#endif
3898}
3899
3900QString QgsAuthManager::passwordHelperRead( bool &ok )
3901{
3902#ifdef HAVE_AUTH
3903 ok = false;
3905
3906 // Retrieve it!
3907 QString password;
3908 passwordHelperLog( tr( "Opening %1 for READ…" ).arg( passwordHelperDisplayName() ) );
3909 QKeychain::ReadPasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3910 QgsSettings settings;
3911 job.setInsecureFallback( settings.value( u"password_helper_insecure_fallback"_s, false, QgsSettings::Section::Auth ).toBool() );
3912 job.setAutoDelete( false );
3913 job.setKey( authPasswordHelperKeyName() );
3914 QEventLoop loop;
3915 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3916 job.start();
3917 loop.exec();
3918 if ( job.error() )
3919 {
3920 mPasswordHelperErrorCode = job.error();
3921 mPasswordHelperErrorMessage = tr( "Retrieving password from the %1 failed: %2." ).arg( passwordHelperDisplayName(), job.errorString() );
3922 // Signals used in the tests to exit main application loop
3923 emit passwordHelperFailure();
3924 }
3925 else
3926 {
3927 password = job.textData();
3928 // Password is there but it is empty, treat it like if it was not found
3929 if ( password.isEmpty() )
3930 {
3931 mPasswordHelperErrorCode = QKeychain::EntryNotFound;
3932 mPasswordHelperErrorMessage = tr( "Empty password retrieved from the %1." ).arg( passwordHelperDisplayName( true ) );
3933 // Signals used in the tests to exit main application loop
3934 emit passwordHelperFailure();
3935 }
3936 else
3937 {
3938 ok = true;
3939 // Signals used in the tests to exit main application loop
3940 emit passwordHelperSuccess();
3941 }
3942 }
3943 passwordHelperProcessError();
3944 return password;
3945#else
3946 Q_UNUSED( ok )
3947 return QString();
3948#endif
3949}
3950
3951bool QgsAuthManager::passwordHelperWrite( const QString &password )
3952{
3953#ifdef HAVE_AUTH
3955
3956 Q_ASSERT( !password.isEmpty() );
3957 bool result;
3958 passwordHelperLog( tr( "Opening %1 for WRITE…" ).arg( passwordHelperDisplayName() ) );
3959 QKeychain::WritePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3960 QgsSettings settings;
3961 job.setInsecureFallback( settings.value( u"password_helper_insecure_fallback"_s, false, QgsSettings::Section::Auth ).toBool() );
3962 job.setAutoDelete( false );
3963 job.setKey( authPasswordHelperKeyName() );
3964 job.setTextData( password );
3965 QEventLoop loop;
3966 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3967 job.start();
3968 loop.exec();
3969 if ( job.error() )
3970 {
3971 mPasswordHelperErrorCode = job.error();
3972 mPasswordHelperErrorMessage = tr( "Storing password in the %1 failed: %2." ).arg( passwordHelperDisplayName(), job.errorString() );
3973 // Signals used in the tests to exit main application loop
3974 emit passwordHelperFailure();
3975 result = false;
3976 }
3977 else
3978 {
3979 passwordHelperClearErrors();
3980 // Signals used in the tests to exit main application loop
3981 emit passwordHelperSuccess();
3982 result = true;
3983 }
3984 passwordHelperProcessError();
3985 return result;
3986#else
3987 Q_UNUSED( password )
3988 return false;
3989#endif
3990}
3991
3993{
3994#ifdef HAVE_AUTH
3995 // Does the user want to store the password in the wallet?
3996 QgsSettings settings;
3997 return settings.value( u"use_password_helper"_s, true, QgsSettings::Section::Auth ).toBool();
3998#else
3999 return false;
4000#endif
4001}
4002
4004{
4005#ifdef HAVE_AUTH
4006 QgsSettings settings;
4007 settings.setValue( u"use_password_helper"_s, enabled, QgsSettings::Section::Auth );
4008 emit messageLog( enabled ? tr( "Your %1 will be <b>used from now</b> on to store and retrieve the master password." )
4009 .arg( passwordHelperDisplayName() ) :
4010 tr( "Your %1 will <b>not be used anymore</b> to store and retrieve the master password." )
4011 .arg( passwordHelperDisplayName() ) );
4012#else
4013 Q_UNUSED( enabled )
4014#endif
4015}
4016
4018{
4019#ifdef HAVE_AUTH
4020 // Does the user want to store the password in the wallet?
4021 QgsSettings settings;
4022 return settings.value( u"password_helper_logging"_s, false, QgsSettings::Section::Auth ).toBool();
4023#else
4024 return false;
4025#endif
4026}
4027
4029{
4030#ifdef HAVE_AUTH
4031 QgsSettings settings;
4032 settings.setValue( u"password_helper_logging"_s, enabled, QgsSettings::Section::Auth );
4033#else
4034 Q_UNUSED( enabled )
4035#endif
4036}
4037
4038void QgsAuthManager::passwordHelperClearErrors()
4039{
4040#ifdef HAVE_AUTH
4041 mPasswordHelperErrorCode = QKeychain::NoError;
4042 mPasswordHelperErrorMessage.clear();
4043#endif
4044}
4045
4046void QgsAuthManager::passwordHelperProcessError()
4047{
4048#ifdef HAVE_AUTH
4050
4051 if ( mPasswordHelperErrorCode == QKeychain::AccessDenied ||
4052 mPasswordHelperErrorCode == QKeychain::AccessDeniedByUser ||
4053 mPasswordHelperErrorCode == QKeychain::NoBackendAvailable ||
4054 mPasswordHelperErrorCode == QKeychain::NotImplemented )
4055 {
4056 // If the error is permanent or the user denied access to the wallet
4057 // we also want to disable the wallet system to prevent annoying
4058 // notification on each subsequent access.
4059 setPasswordHelperEnabled( false );
4060 mPasswordHelperErrorMessage = tr( "There was an error and integration with your %1 has been disabled. "
4061 "You can re-enable it at any time through the \"Utilities\" menu "
4062 "in the Authentication pane of the options dialog. %2" )
4063 .arg( passwordHelperDisplayName(), mPasswordHelperErrorMessage );
4064 }
4065 if ( mPasswordHelperErrorCode != QKeychain::NoError )
4066 {
4067 // We've got an error from the wallet
4068 passwordHelperLog( tr( "Error in %1: %2" ).arg( passwordHelperDisplayName(), mPasswordHelperErrorMessage ) );
4069 emit passwordHelperMessageLog( mPasswordHelperErrorMessage, authManTag(), Qgis::MessageLevel::Critical );
4070 }
4071 passwordHelperClearErrors();
4072#endif
4073}
4074
4075
4076bool QgsAuthManager::masterPasswordInput()
4077{
4078#ifdef HAVE_AUTH
4080
4081 if ( isDisabled() )
4082 return false;
4083
4084 QString pass;
4085 bool storedPasswordIsValid = false;
4086 bool ok = false;
4087
4088 // Read the password from the wallet
4089 if ( passwordHelperEnabled() )
4090 {
4091 bool readOk = false;
4092 pass = passwordHelperRead( readOk );
4093 if ( readOk && ! pass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) )
4094 {
4095 // Let's check the password!
4096 if ( verifyMasterPassword( pass ) )
4097 {
4098 ok = true;
4099 storedPasswordIsValid = true;
4100 }
4101 else
4102 {
4103 emit passwordHelperMessageLog( tr( "Master password stored in the %1 is not valid" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
4104 }
4105 }
4106 }
4107
4108 if ( ! ok )
4109 {
4110 pass.clear();
4112 }
4113
4114 if ( ok && !pass.isEmpty() && mMasterPass != pass )
4115 {
4116 mMasterPass = pass;
4117 if ( passwordHelperEnabled() && ! storedPasswordIsValid )
4118 {
4119 if ( !passwordHelperWrite( pass ) )
4120 {
4121 emit passwordHelperMessageLog( tr( "Master password could not be written to the %1" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
4122 }
4123 }
4124 return true;
4125 }
4126 return false;
4127#else
4128 return false;
4129#endif
4130}
4131
4132bool QgsAuthManager::masterPasswordRowsInDb( int &rows ) const
4133{
4134#ifdef HAVE_AUTH
4135 bool res = false;
4137
4138 if ( isDisabled() )
4139 return res;
4140
4141 rows = 0;
4142
4143 QMutexLocker locker( mMutex.get() );
4144
4145 // Loop through all storages with capability ReadMasterPassword and count the number of master passwords
4147
4148 if ( storages.empty() )
4149 {
4150 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
4151 }
4152 else
4153 {
4154 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4155 {
4156 try
4157 {
4158 rows += storage->masterPasswords( ).count();
4159 // if we successfully queuried at least one storage, the result from this function must be true
4160 res = true;
4161 }
4162 catch ( const QgsNotSupportedException &e )
4163 {
4164 // It should not happen because we are checking the capability in advance
4166 }
4167 }
4168 }
4169
4170 return res;
4171#else
4172 Q_UNUSED( rows )
4173 return false;
4174#endif
4175}
4176
4178{
4179#ifdef HAVE_AUTH
4181
4182 if ( isDisabled() )
4183 return false;
4184
4185 int rows = 0;
4186 if ( !masterPasswordRowsInDb( rows ) )
4187 {
4188 const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
4189 QgsDebugError( err );
4191
4192 return false;
4193 }
4194 return ( rows == 1 );
4195#else
4196 return false;
4197#endif
4198}
4199
4200bool QgsAuthManager::masterPasswordCheckAgainstDb( const QString &compare ) const
4201{
4202#ifdef HAVE_AUTH
4204
4205 if ( isDisabled() )
4206 return false;
4207
4208 // Only check the default DB
4209 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ReadMasterPassword ) )
4210 {
4211 try
4212 {
4213 const QList<QgsAuthConfigurationStorage::MasterPasswordConfig> passwords { defaultStorage->masterPasswords( ) };
4214 if ( passwords.size() == 0 )
4215 {
4216 emit messageLog( tr( "Master password: FAILED to access database" ), authManTag(), Qgis::MessageLevel::Critical );
4217 return false;
4218 }
4219 const QgsAuthConfigurationStorage::MasterPasswordConfig storedPassword { passwords.first() };
4220 return QgsAuthCrypto::verifyPasswordKeyHash( compare.isNull() ? mMasterPass : compare, storedPassword.salt, storedPassword.hash );
4221 }
4222 catch ( const QgsNotSupportedException &e )
4223 {
4224 // It should not happen because we are checking the capability in advance
4226 return false;
4227 }
4228
4229 }
4230 else
4231 {
4232 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
4233 return false;
4234 }
4235#else
4236 Q_UNUSED( compare )
4237 return false;
4238#endif
4239}
4240
4241bool QgsAuthManager::masterPasswordStoreInDb() const
4242{
4243#ifdef HAVE_AUTH
4245
4246 if ( isDisabled() )
4247 return false;
4248
4249 QString salt, hash, civ;
4250 QgsAuthCrypto::passwordKeyHash( mMasterPass, &salt, &hash, &civ );
4251
4252 // Only store in the default DB
4253 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateMasterPassword ) )
4254 {
4255 try
4256 {
4257 return defaultStorage->storeMasterPassword( { salt, civ, hash } );
4258 }
4259 catch ( const QgsNotSupportedException &e )
4260 {
4261 // It should not happen because we are checking the capability in advance
4263 return false;
4264 }
4265 }
4266 else
4267 {
4268 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
4269 return false;
4270 }
4271#else
4272 return false;
4273#endif
4274}
4275
4276bool QgsAuthManager::masterPasswordClearDb()
4277{
4278#ifdef HAVE_AUTH
4280
4281 if ( isDisabled() )
4282 return false;
4283
4284 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::DeleteMasterPassword ) )
4285 {
4286
4287 try
4288 {
4289 return defaultStorage->clearMasterPasswords();
4290 }
4291 catch ( const QgsNotSupportedException &e )
4292 {
4293 // It should not happen because we are checking the capability in advance
4295 return false;
4296 }
4297
4298 }
4299 else
4300 {
4301 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
4302 return false;
4303 }
4304#else
4305 return false;
4306#endif
4307}
4308
4309const QString QgsAuthManager::masterPasswordCiv() const
4310{
4311#ifdef HAVE_AUTH
4313
4314 if ( isDisabled() )
4315 return QString();
4316
4317 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ReadMasterPassword ) )
4318 {
4319 try
4320 {
4321 const QList<QgsAuthConfigurationStorage::MasterPasswordConfig> passwords { defaultStorage->masterPasswords( ) };
4322 if ( passwords.size() == 0 )
4323 {
4324 emit messageLog( tr( "Master password: FAILED to access database" ), authManTag(), Qgis::MessageLevel::Critical );
4325 return QString();
4326 }
4327 return passwords.first().civ;
4328 }
4329 catch ( const QgsNotSupportedException &e )
4330 {
4331 // It should not happen because we are checking the capability in advance
4333 return QString();
4334 }
4335 }
4336 else
4337 {
4338 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
4339 return QString();
4340 }
4341#else
4342 return QString();
4343#endif
4344}
4345
4346QStringList QgsAuthManager::configIds() const
4347{
4348#ifdef HAVE_AUTH
4350
4351 QStringList configKeys = QStringList();
4352
4353 if ( isDisabled() )
4354 return configKeys;
4355
4356 // Loop through all storages with capability ReadConfiguration and get the config ids
4358
4359 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4360 {
4361 try
4362 {
4363 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
4364 // Check if the config ids are already in the list
4365 for ( auto it = configs.cbegin(); it != configs.cend(); ++it )
4366 {
4367 if ( !configKeys.contains( it.key() ) )
4368 {
4369 configKeys.append( it.key() );
4370 }
4371 else
4372 {
4373 emit messageLog( tr( "Config id %1 is already in the list" ).arg( it.key() ), authManTag(), Qgis::MessageLevel::Warning );
4374 }
4375 }
4376 }
4377 catch ( const QgsNotSupportedException &e )
4378 {
4379 // It should not happen because we are checking the capability in advance
4381 }
4382 }
4383
4384 return configKeys;
4385#else
4386 return QStringList();
4387#endif
4388}
4389
4390bool QgsAuthManager::verifyPasswordCanDecryptConfigs() const
4391{
4392#ifdef HAVE_AUTH
4394
4395 if ( isDisabled() )
4396 return false;
4397
4398 // no need to check for setMasterPassword, since this is private and it will be set
4399
4400 // Loop through all storages with capability ReadConfiguration and check if the password can decrypt the configs
4402
4403 for ( const QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4404 {
4405
4406 if ( ! storage->isEncrypted() )
4407 {
4408 continue;
4409 }
4410
4411 try
4412 {
4413 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigsWithPayload();
4414 for ( auto it = configs.cbegin(); it != configs.cend(); ++it )
4415 {
4416 QString configstring( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), it.value().config( u"encrypted_payload"_s ) ) );
4417 if ( configstring.isEmpty() )
4418 {
4419 QgsDebugError( u"Verify password can decrypt configs FAILED, could not decrypt a config (id: %1) from storage %2"_s
4420 .arg( it.key(), storage->name() ) );
4421 return false;
4422 }
4423 }
4424 }
4425 catch ( const QgsNotSupportedException &e )
4426 {
4427 // It should not happen because we are checking the capability in advance
4429 return false;
4430 }
4431
4432 }
4433
4434 if ( storages.empty() )
4435 {
4436 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
4437 return false;
4438 }
4439
4440 return true;
4441#else
4442 return false;
4443#endif
4444}
4445
4446bool QgsAuthManager::reencryptAllAuthenticationConfigs( const QString &prevpass, const QString &prevciv )
4447{
4448#ifdef HAVE_AUTH
4450
4451 if ( isDisabled() )
4452 return false;
4453
4454 bool res = true;
4455 const QStringList ids = configIds();
4456 for ( const auto &configid : ids )
4457 {
4458 res = res && reencryptAuthenticationConfig( configid, prevpass, prevciv );
4459 }
4460 return res;
4461#else
4462 Q_UNUSED( prevpass )
4463 Q_UNUSED( prevciv )
4464 return false;
4465#endif
4466}
4467
4468bool QgsAuthManager::reencryptAuthenticationConfig( const QString &authcfg, const QString &prevpass, const QString &prevciv )
4469{
4470#ifdef HAVE_AUTH
4472
4473 if ( isDisabled() )
4474 return false;
4475
4476 // no need to check for setMasterPassword, since this is private and it will be set
4477
4478 // Loop through all storages with capability ReadConfiguration and reencrypt the config
4480
4481 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4482 {
4483 try
4484 {
4485 if ( storage->methodConfigExists( authcfg ) )
4486 {
4487 if ( ! storage->isEncrypted() )
4488 {
4489 return true;
4490 }
4491
4492 QString payload;
4493 const QgsAuthMethodConfig config = storage->loadMethodConfig( authcfg, payload, true );
4494 if ( payload.isEmpty() || ! config.isValid( true ) )
4495 {
4496 QgsDebugError( u"Reencrypt FAILED, could not find config (id: %1)"_s.arg( authcfg ) );
4497 return false;
4498 }
4499
4500 QString configstring( QgsAuthCrypto::decrypt( prevpass, prevciv, payload ) );
4501 if ( configstring.isEmpty() )
4502 {
4503 QgsDebugError( u"Reencrypt FAILED, could not decrypt config (id: %1)"_s.arg( authcfg ) );
4504 return false;
4505 }
4506
4507 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
4508
4509 if ( !storage->storeMethodConfig( config, configstring ) )
4510 {
4511 emit messageLog( tr( "Store config: FAILED to store config in default storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
4512 return false;
4513 }
4514 return true;
4515 }
4516 }
4517 catch ( const QgsNotSupportedException &e )
4518 {
4519 // It should not happen because we are checking the capability in advance
4521 return false;
4522 }
4523 }
4524
4525 if ( storages.empty() )
4526 {
4527 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
4528 }
4529 else
4530 {
4531 emit messageLog( tr( "Reencrypt FAILED, could not find config (id: %1)" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
4532 }
4533
4534 return false;
4535#else
4536 Q_UNUSED( authcfg )
4537 Q_UNUSED( prevpass )
4538 Q_UNUSED( prevciv )
4539 return false;
4540#endif
4541}
4542
4543bool QgsAuthManager::reencryptAllAuthenticationSettings( const QString &prevpass, const QString &prevciv )
4544{
4546
4547 // TODO: start remove (when function is actually used)
4548 Q_UNUSED( prevpass )
4549 Q_UNUSED( prevciv )
4550 return true;
4551 // end remove
4552
4553#if 0
4554 if ( isDisabled() )
4555 return false;
4556
4558 // When adding settings that require encryption, add to list //
4560
4561 QStringList encryptedsettings;
4562 encryptedsettings << "";
4563
4564 for ( const auto & sett, std::as_const( encryptedsettings ) )
4565 {
4566 if ( sett.isEmpty() || !existsAuthSetting( sett ) )
4567 continue;
4568
4569 // no need to check for setMasterPassword, since this is private and it will be set
4570
4571 QSqlQuery query( authDbConnection() );
4572
4573 query.prepare( QStringLiteral( "SELECT value FROM %1 "
4574 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
4575
4576 query.bindValue( ":setting", sett );
4577
4578 if ( !authDbQuery( &query ) )
4579 return false;
4580
4581 if ( !query.isActive() || !query.isSelect() )
4582 {
4583 QgsDebugError( u"Reencrypt FAILED, query not active or a select operation for setting: %2"_s.arg( sett ) );
4584 return false;
4585 }
4586
4587 if ( query.first() )
4588 {
4589 QString settvalue( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
4590
4591 query.clear();
4592
4593 query.prepare( QStringLiteral( "UPDATE %1 "
4594 "SET value = :value "
4595 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
4596
4597 query.bindValue( ":setting", sett );
4598 query.bindValue( ":value", QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), settvalue ) );
4599
4600 if ( !authDbStartTransaction() )
4601 return false;
4602
4603 if ( !authDbQuery( &query ) )
4604 return false;
4605
4606 if ( !authDbCommit() )
4607 return false;
4608
4609 QgsDebugMsgLevel( u"Reencrypt SUCCESS for setting: %2"_s.arg( sett ), 2 );
4610 return true;
4611 }
4612 else
4613 {
4614 QgsDebugError( u"Reencrypt FAILED, could not find in db setting: %2"_s.arg( sett ) );
4615 return false;
4616 }
4617
4618 if ( query.next() )
4619 {
4620 QgsDebugError( u"Select contains more than one for setting: %1"_s.arg( sett ) );
4621 emit messageOut( tr( "Authentication database contains duplicate setting keys" ), authManTag(), WARNING );
4622 }
4623
4624 return false;
4625 }
4626
4627 return true;
4628#endif
4629}
4630
4631bool QgsAuthManager::reencryptAllAuthenticationIdentities( const QString &prevpass, const QString &prevciv )
4632{
4633#ifdef HAVE_AUTH
4635
4636 if ( isDisabled() )
4637 return false;
4638
4639 bool res = true;
4640 const QStringList ids = certIdentityIds();
4641 for ( const auto &identid : ids )
4642 {
4643 res = res && reencryptAuthenticationIdentity( identid, prevpass, prevciv );
4644 }
4645 return res;
4646#else
4647 Q_UNUSED( prevpass )
4648 Q_UNUSED( prevciv )
4649 return false;
4650#endif
4651}
4652
4653bool QgsAuthManager::reencryptAuthenticationIdentity(
4654 const QString &identid,
4655 const QString &prevpass,
4656 const QString &prevciv )
4657{
4658#ifdef HAVE_AUTH
4660
4661 if ( isDisabled() )
4662 return false;
4663
4664 // no need to check for setMasterPassword, since this is private and it will be set
4665
4666 // Loop through all storages with capability ReadCertificateIdentity and reencrypt the identity
4668
4669
4670 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4671 {
4672
4673 try
4674 {
4675
4676 if ( storage->certIdentityExists( identid ) )
4677 {
4678 if ( ! storage->isEncrypted() )
4679 {
4680 return true;
4681 }
4682
4683 const QPair<QSslCertificate, QString> identityBundle = storage->loadCertIdentityBundle( identid );
4684 QString keystring( QgsAuthCrypto::decrypt( prevpass, prevciv, identityBundle.second ) );
4685 if ( keystring.isEmpty() )
4686 {
4687 QgsDebugError( u"Reencrypt FAILED, could not decrypt identity id: %1"_s.arg( identid ) );
4688 return false;
4689 }
4690
4691 keystring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), keystring );
4692 return storage->storeCertIdentity( identityBundle.first, keystring );
4693 }
4694 }
4695 catch ( const QgsNotSupportedException &e )
4696 {
4697 // It should not happen because we are checking the capability in advance
4699 return false;
4700 }
4701 }
4702
4703 if ( storages.empty() )
4704 {
4705 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
4706 }
4707 else
4708 {
4709 emit messageLog( tr( "Reencrypt FAILED, could not find identity (id: %1)" ).arg( identid ), authManTag(), Qgis::MessageLevel::Critical );
4710 }
4711
4712 return false;
4713#else
4714 Q_UNUSED( identid )
4715 Q_UNUSED( prevpass )
4716 Q_UNUSED( prevciv )
4717 return false;
4718#endif
4719}
4720
4721#ifndef QT_NO_SSL
4722void QgsAuthManager::insertCaCertInCache( QgsAuthCertUtils::CaCertSource source, const QList<QSslCertificate> &certs )
4723{
4724#ifdef HAVE_AUTH
4726
4727 for ( const auto &cert : certs )
4728 {
4729 mCaCertsCache.insert( QgsAuthCertUtils::shaHexForCert( cert ),
4730 QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate>( source, cert ) );
4731 }
4732#else
4733 Q_UNUSED( source )
4734 Q_UNUSED( certs )
4735#endif
4736}
4737#endif
4738
4739QString QgsAuthManager::authPasswordHelperKeyName() const
4740{
4741#ifdef HAVE_AUTH
4743
4744 QString dbProfilePath;
4745
4746 // TODO: get the current profile name from the application
4747
4748 if ( isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
4749 {
4750 const QFileInfo info( mAuthDatabaseConnectionUri );
4751 dbProfilePath = info.dir().dirName();
4752 }
4753 else
4754 {
4755 dbProfilePath = QCryptographicHash::hash( ( mAuthDatabaseConnectionUri.toUtf8() ), QCryptographicHash::Md5 ).toHex();
4756 }
4757
4758 // if not running from the default profile, ensure that a different key is used
4759 return AUTH_PASSWORD_HELPER_KEY_NAME_BASE + ( dbProfilePath.compare( "default"_L1, Qt::CaseInsensitive ) == 0 ? QString() : dbProfilePath );
4760#else
4761 return QString();
4762#endif
4763}
4764
4766{
4767#ifdef HAVE_AUTH
4769 const auto storages = storageRegistry->readyStorages( );
4770 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4771 {
4772 if ( qobject_cast<QgsAuthConfigurationStorageDb *>( storage ) )
4773 {
4774 return static_cast<QgsAuthConfigurationStorageDb *>( storage );
4775 }
4776 }
4777#endif
4778 return nullptr;
4779}
4780
4781QgsAuthConfigurationStorage *QgsAuthManager::firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability capability ) const
4782{
4783#ifdef HAVE_AUTH
4785 return storageRegistry->firstReadyStorageWithCapability( capability );
4786#else
4787 Q_UNUSED( capability )
4788 return nullptr;
4789#endif
4790}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:159
@ Warning
Warning message.
Definition qgis.h:161
@ Critical
Critical/error message.
Definition qgis.h:162
@ Info
Information message.
Definition qgis.h:160
AuthConfigurationStorageCapability
Authentication configuration storage capabilities.
Definition qgis.h:105
@ CreateSetting
Can create a new authentication setting.
Definition qgis.h:141
@ CreateConfiguration
Can create a new authentication configuration.
Definition qgis.h:111
@ ClearStorage
Can clear all configurations from storage.
Definition qgis.h:106
@ DeleteCertificateAuthority
Can delete a certificate authority.
Definition qgis.h:125
@ DeleteSslCertificateCustomConfig
Can delete a SSL certificate custom config.
Definition qgis.h:120
@ DeleteSetting
Can delete the authentication setting.
Definition qgis.h:140
@ ReadSslCertificateCustomConfig
Can read a SSL certificate custom config.
Definition qgis.h:118
@ DeleteMasterPassword
Can delete the master password.
Definition qgis.h:135
@ CreateSslCertificateCustomConfig
Can create a new SSL certificate custom config.
Definition qgis.h:121
@ ReadCertificateTrustPolicy
Can read a certificate trust policy.
Definition qgis.h:128
@ ReadConfiguration
Can read an authentication configuration.
Definition qgis.h:108
@ UpdateConfiguration
Can update an authentication configuration.
Definition qgis.h:109
@ ReadCertificateAuthority
Can read a certificate authority.
Definition qgis.h:123
@ CreateCertificateAuthority
Can create a new certificate authority.
Definition qgis.h:126
@ DeleteConfiguration
Can deleet an authentication configuration.
Definition qgis.h:110
@ ReadSetting
Can read the authentication settings.
Definition qgis.h:138
@ CreateCertificateIdentity
Can create a new certificate identity.
Definition qgis.h:116
@ ReadCertificateIdentity
Can read a certificate identity.
Definition qgis.h:113
@ CreateCertificateTrustPolicy
Can create a new certificate trust policy.
Definition qgis.h:131
@ ReadMasterPassword
Can read the master password.
Definition qgis.h:133
@ CreateMasterPassword
Can create a new master password.
Definition qgis.h:136
@ DeleteCertificateTrustPolicy
Can delete a certificate trust policy.
Definition qgis.h:130
CertTrustPolicy
Type of certificate trust policy.
CaCertSource
Type of CA certificate source.
Configuration container for SSL server connection exceptions or overrides.
bool isNull() const
Whether configuration is null (missing components).
const QList< QSslError::SslError > sslIgnoredErrorEnums() const
SSL server errors (as enum list) to ignore in connections.
const QSslCertificate sslCertificate() const
Server certificate object.
const QString sslHostPort() const
Server host:port string.
QSqlDatabase based implementation of QgsAuthConfigurationStorage.
bool removeCertTrustPolicy(const QSslCertificate &cert) override
Remove certificate trust policy.
const QgsAuthConfigSslServer loadSslCertCustomConfigByHost(const QString &hostport) const override
Loads an SSL certificate custom config by hostport (host:port).
QString loadAuthSetting(const QString &key) const override
Load an authentication setting from the storage.
bool removeAuthSetting(const QString &key) override
Remove an authentication setting from the storage.
const QMap< QString, QgsAuthCertUtils::CertTrustPolicy > caCertsPolicy() const override
Returns the map of CA certificates hashes in the storages and their trust policy.
QgsAuthCertUtils::CertTrustPolicy loadCertTrustPolicy(const QSslCertificate &cert) const override
Load certificate trust policy.
bool sslCertCustomConfigExists(const QString &id, const QString &hostport) override
Check if SSL certificate custom config exists.
bool removeCertIdentity(const QSslCertificate &cert) override
Remove a certificate identity from the storage.
const QPair< QSslCertificate, QString > loadCertIdentityBundle(const QString &id) const override
Returns a certificate identity bundle by id (sha hash).
const QList< QgsAuthConfigurationStorage::MasterPasswordConfig > masterPasswords() const override
Returns the list of (encrypted) master passwords stored in the database.
bool methodConfigExists(const QString &id) const override
Check if an authentication configuration exists in the storage.
QStringList certIdentityIds() const override
certIdentityIds get list of certificate identity ids from database
bool initialize() override
Initializes the storage.
bool storeMethodConfig(const QgsAuthMethodConfig &mconfig, const QString &payload) override
Store an authentication config in the database.
bool removeCertAuthority(const QSslCertificate &cert) override
Remove a certificate authority.
const QSslCertificate loadCertIdentity(const QString &id) const override
certIdentity get a certificate identity by id (sha hash)
const QList< QgsAuthConfigSslServer > sslCertCustomConfigs() const override
sslCertCustomConfigs get SSL certificate custom configs
QgsAuthMethodConfigsMap authMethodConfigs(const QStringList &allowedMethods=QStringList()) const override
Returns a mapping of authentication configurations available from this storage.
const QList< QSslCertificate > caCerts() const override
Returns the list of CA certificates in the storage.
bool certTrustPolicyExists(const QSslCertificate &cert) const override
Check if certificate trust policy exists.
const QSslCertificate loadCertAuthority(const QString &id) const override
certAuthority get a certificate authority by id (sha hash)
bool removeMethodConfig(const QString &id) override
Removes the authentication configuration with the specified id.
QgsAuthMethodConfigsMap authMethodConfigsWithPayload() const override
Returns a mapping of authentication configurations available from this storage.
bool certIdentityExists(const QString &id) const override
Check if the certificate identity exists.
bool certAuthorityExists(const QSslCertificate &cert) const override
Check if a certificate authority exists.
QgsAuthMethodConfig loadMethodConfig(const QString &id, QString &payload, bool full=false) const override
Load an authentication configuration from the database.
bool storeCertIdentity(const QSslCertificate &cert, const QString &keyPem) override
Store a certificate identity in the storage.
bool removeSslCertCustomConfig(const QString &id, const QString &hostport) override
Remove an SSL certificate custom config.
const QList< QSslCertificate > certIdentities() const override
certIdentities get certificate identities
QString name() const override
Returns a human readable localized short name of the storage implementation (e.g "SQLite").
bool authSettingExists(const QString &key) const override
Check if an authentication setting exists in the storage.
const QgsAuthConfigSslServer loadSslCertCustomConfig(const QString &id, const QString &hostport) const override
Loads an SSL certificate custom config by id (sha hash) and hostport (host:port).
Registry for authentication configuration storages.
QgsAuthConfigurationStorage * firstReadyStorageWithCapability(Qgis::AuthConfigurationStorageCapability capability) const
Returns the first ready (and enabled) authentication configuration storage which has the required cap...
QList< QgsAuthConfigurationStorage * > storages() const
Returns the list of all registered authentication configuration storages.
QList< QgsAuthConfigurationStorage * > readyStoragesWithCapability(Qgis::AuthConfigurationStorageCapability capability) const
Returns the list of all ready (and enabled) authentication configuration storage with the required ca...
QList< QgsAuthConfigurationStorage * > readyStorages() const
Returns the list of all ready (and enabled) authentication configuration storage.
bool addStorage(QgsAuthConfigurationStorage *storage)
Add an authentication configuration storage to the registry.
Abstract class that defines the interface for all authentication configuration storage implementation...
void messageLog(const QString &message, const QString &tag=u"Authentication"_s, Qgis::MessageLevel level=Qgis::MessageLevel::Info)
Custom logging signal to relay to console output and QgsMessageLog.
virtual void setReadOnly(bool readOnly)
Utility method to unset all editing capabilities.
void methodConfigChanged()
Emitted when the storage method config table was changed.
Qgis::AuthConfigurationStorageCapabilities capabilities() const
Returns the capabilities of the storage.
bool isEnabled() const
Returns true if the storage is enabled.
bool isEncrypted() const
Returns true if the storage is encrypted.
virtual QString lastError() const
Returns the last error message.
static void passwordKeyHash(const QString &pass, QString *salt, QString *hash, QString *cipheriv=nullptr)
Generate SHA256 hash for master password, with iterations and salt.
static const QString encrypt(const QString &pass, const QString &cipheriv, const QString &text)
Encrypt data using master password.
static bool verifyPasswordKeyHash(const QString &pass, const QString &salt, const QString &hash, QString *hashderived=nullptr)
Verify existing master password hash to a re-generated one.
static const QString decrypt(const QString &pass, const QString &cipheriv, const QString &text)
Decrypt data using master password.
Singleton which offers an interface to manage the authentication configuration database and to utiliz...
bool storeAuthSetting(const QString &key, const QVariant &value, bool encrypt=false)
Stores an authentication setting.
bool setDefaultCertTrustPolicy(QgsAuthCertUtils::CertTrustPolicy policy)
Sets the default certificate trust policy preferred by user.
void clearAllCachedConfigs()
Clear all authentication configs from authentication method caches.
const QSslCertificate certIdentity(const QString &id)
certIdentity get a certificate identity by id (sha hash)
const QStringList certIdentityBundleToPem(const QString &id)
certIdentityBundleToPem get a certificate identity bundle by id (sha hash) returned as PEM text
bool updateIgnoredSslErrorsCache(const QString &shahostport, const QList< QSslError > &errors)
Update ignored SSL error cache with possible ignored SSL errors, using sha:host:port key.
bool verifyMasterPassword(const QString &compare=QString())
Verify the supplied master password against any existing hash in authentication database.
bool updateIgnoredSslErrorsCacheFromConfig(const QgsAuthConfigSslServer &config)
Update ignored SSL error cache with possible ignored SSL errors, using server config.
const QString disabledMessage() const
Standard message for when QCA's qca-ossl plugin is missing and system is disabled.
const QList< QSslCertificate > trustedCaCertsCache()
trustedCaCertsCache cache of trusted certificate authorities, ready for network connections
QgsAuthMethod * configAuthMethod(const QString &authcfg)
Gets authentication method from the config/provider cache.
static bool isFilesystemBasedDatabase(const QString &uri)
Returns the true if the uri is a filesystem-based database (SQLite).
bool storeCertIdentity(const QSslCertificate &cert, const QSslKey &key)
Store a certificate identity.
QgsAuthMethodsMap authMethodsMap(const QString &dataprovider=QString())
Gets available authentication methods mapped to their key.
bool rebuildIgnoredSslErrorCache()
Rebuild ignoredSSL error cache.
bool initSslCaches()
Initialize various SSL authentication caches.
const QList< QSslCertificate > extraFileCAs()
extraFileCAs extra file-based certificate authorities
bool removeAuthSetting(const QString &key)
Remove an authentication setting.
bool storeCertTrustPolicy(const QSslCertificate &cert, QgsAuthCertUtils::CertTrustPolicy policy)
Store user trust value for a certificate.
bool rebuildCaCertsCache()
Rebuild certificate authority cache.
bool scheduledAuthDatabaseErase()
Whether there is a scheduled opitonal erase of authentication database.
bool eraseAuthenticationDatabase(bool backup, QString *backuppath=nullptr)
Erase all rows from all tables in authentication database.
static bool passwordHelperEnabled()
Password helper enabled getter.
void passwordHelperMessageLog(const QString &message, const QString &tag=QgsAuthManager::AUTH_MAN_TAG, Qgis::MessageLevel level=Qgis::MessageLevel::Info)
Custom logging signal to inform the user about master password <-> password manager interactions.
bool exportAuthenticationConfigsToXml(const QString &filename, const QStringList &authcfgs, const QString &password=QString())
Export authentication configurations to an XML file.
QString sqliteDatabasePath() const
Returns the path to the authentication database file or an empty string if the database is not SQLite...
Q_DECL_DEPRECATED bool init(const QString &pluginPath=QString(), const QString &authDatabasePath=QString())
init initialize QCA, prioritize qca-ossl plugin and optionally set up the authentication database
void authDatabaseChanged()
Emitted when the authentication db is significantly changed, e.g. large record removal,...
void setPasswordHelperEnabled(bool enabled)
Password helper enabled setter.
void setScheduledAuthDatabaseErase(bool scheduleErase)
Schedule an optional erase of authentication database, starting when mutex is lockable.
const QList< QgsAuthConfigSslServer > sslCertCustomConfigs()
sslCertCustomConfigs get SSL certificate custom configs
const QList< QSslCertificate > untrustedCaCerts(QList< QSslCertificate > trustedCAs=QList< QSslCertificate >())
untrustedCaCerts get list of untrusted certificate authorities
const QString uniqueConfigId() const
Gets a unique generated 7-character string to assign to as config id.
const QPair< QSslCertificate, QSslKey > certIdentityBundle(const QString &id)
Gets a certificate identity bundle by id (sha hash).
bool isDisabled() const
Whether QCA has the qca-ossl plugin, which a base run-time requirement.
QVariant authSetting(const QString &key, const QVariant &defaultValue=QVariant(), bool decrypt=false)
Returns a previously set authentication setting.
static const QString AUTH_MAN_TAG
The display name of the Authentication Manager.
QgsAuthCertUtils::CertTrustPolicy defaultCertTrustPolicy()
Gets the default certificate trust policy preferred by user.
const QByteArray trustedCaCertsPemText()
trustedCaCertsPemText get concatenated string of all trusted CA certificates
static bool hasConfigId(const QString &txt)
Returns whether a string includes an authcfg ID token.
bool removeAllAuthenticationConfigs()
Clear all authentication configs from table in database and from provider caches.
QgsAuthCertUtils::CertTrustPolicy certificateTrustPolicy(const QSslCertificate &cert)
certificateTrustPolicy get trust policy for a particular certificate cert
static bool passwordHelperLoggingEnabled()
Password helper logging enabled getter.
QgsAuthConfigurationStorageRegistry * authConfigurationStorageRegistry() const
Returns the authentication configuration storage registry.
bool rebuildCertTrustCache()
Rebuild certificate authority cache.
Q_DECL_DEPRECATED const QString authenticationDatabasePath() const
The standard authentication database file in ~/.qgis3/ or defined location.
static const QList< QSslCertificate > systemRootCAs()
systemRootCAs get root system certificate authorities
bool removeCertAuthority(const QSslCertificate &cert)
Remove a certificate authority.
const QList< QSslCertificate > trustedCaCerts(bool includeinvalid=false)
trustedCaCerts get list of all trusted CA certificates
bool existsCertAuthority(const QSslCertificate &cert)
Check if a certificate authority exists.
const QMap< QString, QSslCertificate > mappedDatabaseCAs()
mappedDatabaseCAs get sha1-mapped database-stored certificate authorities
bool importAuthenticationConfigsFromXml(const QString &filename, const QString &password=QString(), bool overwrite=false)
Import authentication configurations from an XML file.
bool configIdUnique(const QString &id) const
Verify if provided authentication id is unique.
static const QgsSettingsEntryBool * settingsGenerateRandomPasswordForPasswordHelper
QStringList configIds() const
Gets list of authentication ids from database.
QString authManTag() const
Simple text tag describing authentication system for message logs.
bool loadAuthenticationConfig(const QString &authcfg, QgsAuthMethodConfig &mconfig, bool full=false)
Load an authentication config from the database into subclass.
QgsAuthCertUtils::CertTrustPolicy certTrustPolicy(const QSslCertificate &cert)
certTrustPolicy get whether certificate cert is trusted by user
bool masterPasswordHashInDatabase() const
Verify a password hash existing in authentication database.
Q_DECL_DEPRECATED void messageOut(const QString &message, const QString &tag=QgsAuthManager::AUTH_MAN_TAG, QgsAuthManager::MessageLevel level=QgsAuthManager::INFO) const
Custom logging signal to relay to console output and QgsMessageLog.
QgsAuthConfigurationStorageDb * defaultDbStorage() const
Transitional proxy to the first ready storage of database type.
bool updateNetworkProxy(QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkProxy with an authentication config.
const QSslCertificate certAuthority(const QString &id)
Gets a certificate authority by id (sha hash).
void passwordHelperSuccess()
Signals emitted on password helper success, mainly used in the tests to exit main application loop.
bool registerCoreAuthMethods()
Instantiate and register existing C++ core authentication methods from plugins.
bool passwordHelperDelete()
Delete master password from wallet.
~QgsAuthManager() override
void dumpIgnoredSslErrorsCache_()
Utility function to dump the cache for debug purposes.
const QList< QSslCertificate > databaseCAs()
databaseCAs get database-stored certificate authorities
void messageLog(const QString &message, const QString &tag=QgsAuthManager::AUTH_MAN_TAG, Qgis::MessageLevel level=Qgis::MessageLevel::Info) const
Custom logging signal to relay to console output and QgsMessageLog.
bool backupAuthenticationDatabase(QString *backuppath=nullptr)
Close connection to current authentication database and back it up.
void authDatabaseEraseRequested()
Emitted when a user has indicated they may want to erase the authentication db.
void passwordHelperFailure()
Signals emitted on password helper failure, mainly used in the tests to exit main application loop.
bool existsSslCertCustomConfig(const QString &id, const QString &hostport)
Check if SSL certificate custom config exists.
bool existsAuthSetting(const QString &key)
Check if an authentication setting exists.
void clearCachedConfig(const QString &authcfg)
Clear an authentication config from its associated authentication method cache.
void clearMasterPassword()
Clear supplied master password.
bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkRequest with an authentication config.
bool createAndStoreRandomMasterPasswordInKeyChain()
Creates a new securely seeded random password and stores it in the system keychain as the new master ...
const QList< QSslCertificate > certIdentities()
certIdentities get certificate identities
bool storeCertAuthority(const QSslCertificate &cert)
Store a certificate authority.
QStringList certIdentityIds() const
certIdentityIds get list of certificate identity ids from database
bool removeCertTrustPolicies(const QList< QSslCertificate > &certs)
Remove a group certificate authorities.
QgsAuthMethod * authMethod(const QString &authMethodKey)
Gets authentication method from the config/provider cache via its key.
bool updateDataSourceUriItems(QStringList &connectionItems, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QgsDataSourceUri with an authentication config.
void setup(const QString &pluginPath=QString(), const QString &authDatabasePath=QString())
Sets up the authentication manager configuration.
const QString passwordHelperErrorMessage()
Error message getter.
Q_DECL_DEPRECATED QSqlDatabase authDatabaseConnection() const
Sets up the application instance of the authentication database connection.
void updateConfigAuthMethods()
Sync the confg/authentication method cache with what is in database.
bool storeSslCertCustomConfig(const QgsAuthConfigSslServer &config)
Store an SSL certificate custom config.
static void setPasswordHelperLoggingEnabled(bool enabled)
Password helper logging enabled setter.
bool ensureInitialized() const
Performs lazy initialization of the authentication framework, if it has not already been done.
const QgsAuthConfigSslServer sslCertCustomConfigByHost(const QString &hostport)
sslCertCustomConfigByHost get an SSL certificate custom config by hostport (host:port)
bool updateAuthenticationConfig(const QgsAuthMethodConfig &config)
Update an authentication config in the database.
bool existsCertIdentity(const QString &id)
Check if a certificate identity exists.
const QString authenticationDatabaseUri() const
Returns the authentication database connection URI.
static const QgsSettingsEntryBool * settingsUsingGeneratedRandomPassword
bool resetMasterPassword(const QString &newpass, const QString &oldpass, bool keepbackup, QString *backuppath=nullptr)
Reset the master password to a new one, then re-encrypts all previous configs with the new password.
QStringList authMethodsKeys(const QString &dataprovider=QString())
Gets keys of supported authentication methods.
bool passwordHelperSync()
Store the password manager into the wallet.
bool masterPasswordIsSet() const
Whether master password has be input and verified, i.e. authentication database is accessible.
const QString methodConfigTableName() const
Returns the database table from the first ready storage that stores authentication configs,...
static QgsAuthManager * instance()
Enforce singleton pattern.
void masterPasswordVerified(bool verified)
Emitted when a password has been verify (or not).
bool setMasterPassword(bool verify=false)
Main call to initially set or continually check master password is set.
bool storeCertAuthorities(const QList< QSslCertificate > &certs)
Store multiple certificate authorities.
bool removeSslCertCustomConfig(const QString &id, const QString &hostport)
Remove an SSL certificate custom config.
bool updateNetworkReply(QNetworkReply *reply, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkReply with an authentication config (used to skip known SSL errors,...
bool rebuildTrustedCaCertsCache()
Rebuild trusted certificate authorities cache.
const QgsAuthMethodMetadata * authMethodMetadata(const QString &authMethodKey)
Gets authentication method metadata via its key.
bool removeAuthenticationConfig(const QString &authcfg)
Remove an authentication config in the database.
bool removeCertTrustPolicy(const QSslCertificate &cert)
Remove a certificate authority.
const QString authenticationDatabaseUriStripped() const
Returns the authentication database connection URI with the password stripped.
QgsAuthMethod::Expansions supportedAuthMethodExpansions(const QString &authcfg)
Gets supported authentication method expansion(s), e.g.
const QgsAuthConfigSslServer sslCertCustomConfig(const QString &id, const QString &hostport)
sslCertCustomConfig get an SSL certificate custom config by id (sha hash) and hostport (host:port)
QgsAuthMethodConfigsMap availableAuthMethodConfigs(const QString &dataprovider=QString())
Gets mapping of authentication config ids and their base configs (not decrypted data).
bool masterPasswordSame(const QString &password) const
Check whether supplied password is the same as the one already set.
static const QString AUTH_PASSWORD_HELPER_DISPLAY_NAME
The display name of the password helper (platform dependent).
bool storeAuthenticationConfig(QgsAuthMethodConfig &mconfig, bool overwrite=false)
Store an authentication config in the database.
bool verifyStoredPasswordHelperPassword()
Verify the password stored in the password helper.
bool removeCertIdentity(const QString &id)
Remove a certificate identity.
static QString passwordHelperDisplayName(bool titleCase=false)
Returns a translated display name of the password helper (platform dependent).
bool resetMasterPasswordUsingStoredPasswordHelper(const QString &newPassword, bool keepBackup, QString *backupPath=nullptr)
Reset the master password to a new one, hen re-encrypts all previous configs with the new password.
QString configAuthMethodKey(const QString &authcfg) const
Gets key of authentication method associated with config ID.
Configuration storage class for authentication method configurations.
bool isValid(bool validateid=false) const
Whether the configuration is valid.
bool readXml(const QDomElement &element)
from a DOM element.
const QString configString() const
The extended configuration, as stored and retrieved from the authentication database.
const QString id() const
Gets 'authcfg' 7-character alphanumeric ID of the config.
void loadConfigString(const QString &configstr)
Load existing extended configuration.
bool writeXml(QDomElement &parentElement, QDomDocument &document)
Stores the configuration in a DOM.
void setId(const QString &id)
Sets auth config ID.
Holds data auth method key, description, and associated shared library file information.
const QgsAuthMethodMetadata * authMethodMetadata(const QString &authMethodKey) const
Returns metadata of the auth method or nullptr if not found.
static QgsAuthMethodRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QStringList authMethodList() const
Returns list of available auth methods by their keys.
Abstract base class for authentication method plugins.
virtual bool updateNetworkProxy(QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider=QString())
Update proxy settings with authentication components.
virtual bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Update a network request with authentication components.
QgsAuthMethod::Expansions supportedExpansions() const
Flags that represent the update points (where authentication configurations are expanded) supported b...
virtual void clearCachedConfig(const QString &authcfg)=0
Clear any cached configuration.
virtual void updateMethodConfig(QgsAuthMethodConfig &mconfig)=0
Update an authentication configuration in place.
virtual bool updateNetworkReply(QNetworkReply *reply, const QString &authcfg, const QString &dataprovider=QString())
Update a network reply with authentication components.
virtual bool updateDataSourceUriItems(QStringList &connectionItems, const QString &authcfg, const QString &dataprovider=QString())
Update data source connection items with authentication components.
QFlags< Expansion > Expansions
static QgsCredentials * instance()
retrieves instance
bool getMasterPassword(QString &password, bool stored=false)
QString what() const
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
Custom exception class which is raised when an operation is not supported.
Scoped object for logging of the runtime for a single operation or group of operations.
A boolean settings entry.
static QgsSettingsTreeNode * sTreeAuthentication
Stores settings for use within QGIS.
Definition qgssettings.h:68
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7451
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7450
QHash< QString, QgsAuthMethodConfig > QgsAuthMethodConfigsMap
QHash< QString, QgsAuthMethod * > QgsAuthMethodsMap
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59