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