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