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