QGIS API Documentation 4.3.0-Master (16eddb2670e)
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 QMap<QString, QVariant> storageSettings = storage->settings();
1513 QString masterPassword;
1514 QString masterPwCiv;
1515 if ( storageSettings.contains( "masterPassword" ) )
1516 {
1517 masterPassword = storageSettings.value( "masterPassword" ).toString();
1518 }
1519 else
1520 {
1521 masterPassword = mMasterPass;
1522 }
1523 if ( storageSettings.contains( "masterPasswordCiv" ) )
1524 {
1525 masterPwCiv = storageSettings.value( "masterPasswordCiv" ).toString();
1526 }
1527 else
1528 {
1529 masterPwCiv = masterPasswordCiv();
1530 }
1531 payload = QgsAuthCrypto::decrypt( masterPassword, masterPwCiv, payload );
1532 }
1533 config.loadConfigString( payload );
1534 }
1535
1536 QString authMethodKey = configAuthMethodKey( authcfg );
1537 QgsAuthMethod *authmethod = authMethod( authMethodKey );
1538 if ( authmethod )
1539 {
1540 authmethod->updateMethodConfig( config );
1541 }
1542 else
1543 {
1544 QgsDebugError( u"Update of authcfg %1 FAILED for auth method %2"_s.arg( authcfg, authMethodKey ) );
1545 }
1546
1547 QgsDebugMsgLevel( u"Load %1 config SUCCESS for authcfg: %2"_s.arg( full ? "full" : "base", authcfg ), 2 );
1548 return true;
1549 }
1550 }
1551
1552 if ( storages.empty() )
1553 {
1554 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1555 }
1556 else
1557 {
1558 emit messageLog( tr( "Load config: FAILED to load config %1 from any storage" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
1559 }
1560
1561 return false;
1562#else
1563 Q_UNUSED( authcfg )
1564 Q_UNUSED( config )
1565 Q_UNUSED( full )
1566 return false;
1567#endif
1568}
1569
1571{
1572#ifdef HAVE_AUTH
1574
1575 QMutexLocker locker( mMutex.get() );
1576 if ( isDisabled() )
1577 return false;
1578
1579 if ( authcfg.isEmpty() )
1580 return false;
1581
1582 // Loop through all storages with capability DeleteConfiguration and delete the first one that has the config
1584
1585 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1586 {
1587 if ( storage->methodConfigExists( authcfg ) )
1588 {
1589 if ( !storage->removeMethodConfig( authcfg ) )
1590 {
1591 emit messageLog( tr( "Remove config: FAILED to remove config from the storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1592 return false;
1593 }
1594 else
1595 {
1596 clearCachedConfig( authcfg );
1598 QgsDebugMsgLevel( u"REMOVED config for authcfg: %1"_s.arg( authcfg ), 2 );
1599 return true;
1600 }
1601 break;
1602 }
1603 }
1604
1605 if ( storages.empty() )
1606 {
1607 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1608 }
1609 else
1610 {
1611 emit messageLog( tr( "Remove config: FAILED to remove config %1 from any storage" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
1612 }
1613
1614 return false;
1615
1616#else
1617 Q_UNUSED( authcfg )
1618 return false;
1619#endif
1620}
1621
1622bool QgsAuthManager::exportAuthenticationConfigsToXml( const QString &filename, const QStringList &authcfgs, const QString &password )
1623{
1624#ifdef HAVE_AUTH
1626
1627 if ( filename.isEmpty() )
1628 return false;
1629
1630 QDomDocument document( u"qgis_authentication"_s );
1631 QDomElement root = document.createElement( u"qgis_authentication"_s );
1632 document.appendChild( root );
1633
1634 QString civ;
1635 if ( !password.isEmpty() )
1636 {
1637 QString salt;
1638 QString hash;
1639 QgsAuthCrypto::passwordKeyHash( password, &salt, &hash, &civ );
1640 root.setAttribute( u"salt"_s, salt );
1641 root.setAttribute( u"hash"_s, hash );
1642 root.setAttribute( u"civ"_s, civ );
1643 }
1644
1645 QDomElement configurations = document.createElement( u"configurations"_s );
1646 for ( const QString &authcfg : authcfgs )
1647 {
1648 QgsAuthMethodConfig authMethodConfig;
1649
1650 bool ok = loadAuthenticationConfig( authcfg, authMethodConfig, true );
1651 if ( ok )
1652 {
1653 authMethodConfig.writeXml( configurations, document );
1654 }
1655 }
1656 if ( !password.isEmpty() )
1657 {
1658 QString configurationsString;
1659 QTextStream ts( &configurationsString );
1660 configurations.save( ts, 2 );
1661 root.appendChild( document.createTextNode( QgsAuthCrypto::encrypt( password, civ, configurationsString ) ) );
1662 }
1663 else
1664 {
1665 root.appendChild( configurations );
1666 }
1667
1668 QFile file( filename );
1669 if ( !file.open( QFile::WriteOnly | QIODevice::Truncate ) )
1670 return false;
1671
1672 QTextStream ts( &file );
1673 document.save( ts, 2 );
1674 file.close();
1675 return true;
1676#else
1677 Q_UNUSED( filename )
1678 Q_UNUSED( authcfgs )
1679 Q_UNUSED( password )
1680 return false;
1681#endif
1682}
1683
1684bool QgsAuthManager::importAuthenticationConfigsFromXml( const QString &filename, const QString &password, bool overwrite )
1685{
1686#ifdef HAVE_AUTH
1688
1689 QFile file( filename );
1690 if ( !file.open( QFile::ReadOnly ) )
1691 {
1692 return false;
1693 }
1694
1695 QDomDocument document( u"qgis_authentication"_s );
1696 if ( !document.setContent( &file ) )
1697 {
1698 file.close();
1699 return false;
1700 }
1701 file.close();
1702
1703 QDomElement root = document.documentElement();
1704 if ( root.tagName() != "qgis_authentication"_L1 )
1705 {
1706 return false;
1707 }
1708
1709 QDomElement configurations;
1710 if ( root.hasAttribute( u"salt"_s ) )
1711 {
1712 QString salt = root.attribute( u"salt"_s );
1713 QString hash = root.attribute( u"hash"_s );
1714 QString civ = root.attribute( u"civ"_s );
1715 if ( !QgsAuthCrypto::verifyPasswordKeyHash( password, salt, hash ) )
1716 return false;
1717
1718 document.setContent( QgsAuthCrypto::decrypt( password, civ, root.text() ) );
1719 configurations = document.firstChild().toElement();
1720 }
1721 else
1722 {
1723 configurations = root.firstChildElement( u"configurations"_s );
1724 }
1725
1726 QDomElement configuration = configurations.firstChildElement();
1727 while ( !configuration.isNull() )
1728 {
1729 QgsAuthMethodConfig authMethodConfig;
1730 ( void ) authMethodConfig.readXml( configuration );
1731 storeAuthenticationConfig( authMethodConfig, overwrite );
1732
1733 configuration = configuration.nextSiblingElement();
1734 }
1735 return true;
1736#else
1737 Q_UNUSED( filename )
1738 Q_UNUSED( password )
1739 Q_UNUSED( overwrite )
1740 return false;
1741#endif
1742}
1743
1745{
1746#ifdef HAVE_AUTH
1748
1749 QMutexLocker locker( mMutex.get() );
1750 if ( isDisabled() )
1751 return false;
1752
1753 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::DeleteConfiguration ) )
1754 {
1755 if ( defaultStorage->clearMethodConfigs() )
1756 {
1759 QgsDebugMsgLevel( u"REMOVED all configs from the default storage"_s, 2 );
1760 return true;
1761 }
1762 else
1763 {
1764 QgsDebugMsgLevel( u"FAILED to remove all configs from the default storage"_s, 2 );
1765 return false;
1766 }
1767 }
1768 else
1769 {
1770 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1771 return false;
1772 }
1773#else
1774 return false;
1775#endif
1776}
1777
1778
1780{
1781#ifdef HAVE_AUTH
1783
1784 QMutexLocker locker( mMutex.get() );
1785
1786 if ( sqliteDatabasePath().isEmpty() )
1787 {
1788 const char *err = QT_TR_NOOP( "The authentication storage is not filesystem-based" );
1789 QgsDebugError( err );
1791 return false;
1792 }
1793
1794 if ( !QFile::exists( sqliteDatabasePath() ) )
1795 {
1796 const char *err = QT_TR_NOOP( "No authentication database file found" );
1797 QgsDebugError( err );
1799 return false;
1800 }
1801
1802 // close any connection to current db
1804 QSqlDatabase authConn = authDatabaseConnection();
1806 if ( authConn.isValid() && authConn.isOpen() )
1807 authConn.close();
1808
1809 // duplicate current db file to 'qgis-auth_YYYY-MM-DD-HHMMSS.db' backup
1810 QString datestamp( QDateTime::currentDateTime().toString( u"yyyy-MM-dd-hhmmss"_s ) );
1811 QString dbbackup( sqliteDatabasePath() );
1812 dbbackup.replace( ".db"_L1, u"_%1.db"_s.arg( datestamp ) );
1813
1814 if ( !QFile::copy( sqliteDatabasePath(), dbbackup ) )
1815 {
1816 const char *err = QT_TR_NOOP( "Could not back up authentication database" );
1817 QgsDebugError( err );
1819 return false;
1820 }
1821
1822 if ( backuppath )
1823 *backuppath = dbbackup;
1824
1825 QgsDebugMsgLevel( u"Backed up auth database at %1"_s.arg( dbbackup ), 2 );
1826 return true;
1827#else
1828 Q_UNUSED( backuppath )
1829 return false;
1830#endif
1831}
1832
1833bool QgsAuthManager::eraseAuthenticationDatabase( bool backup, QString *backuppath )
1834{
1835#ifdef HAVE_AUTH
1837
1838 QMutexLocker locker( mMutex.get() );
1839 if ( isDisabled() )
1840 return false;
1841
1842 QString dbbackup;
1843 if ( backup && !backupAuthenticationDatabase( &dbbackup ) )
1844 {
1845 emit messageLog( tr( "Failed to backup authentication database" ), authManTag(), Qgis::MessageLevel::Warning );
1846 return false;
1847 }
1848
1849 if ( backuppath && !dbbackup.isEmpty() )
1850 *backuppath = dbbackup;
1851
1852 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ClearStorage ) )
1853 {
1854 if ( defaultStorage->erase() )
1855 {
1856 mMasterPass = QString();
1859 QgsDebugMsgLevel( u"ERASED all configs"_s, 2 );
1860 return true;
1861 }
1862 else
1863 {
1864 QgsDebugMsgLevel( u"FAILED to erase all configs"_s, 2 );
1865 return false;
1866 }
1867 }
1868 else
1869 {
1870 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1871 return false;
1872 }
1873
1874#ifndef QT_NO_SSL
1875 initSslCaches();
1876#endif
1877
1878 emit authDatabaseChanged();
1879
1880 return true;
1881#else
1882 Q_UNUSED( backup )
1883 Q_UNUSED( backuppath )
1884 return false;
1885#endif
1886}
1887
1888bool QgsAuthManager::updateNetworkRequest( QNetworkRequest &request, const QString &authcfg, const QString &dataprovider )
1889{
1890#ifdef HAVE_AUTH
1892
1893 if ( isDisabled() )
1894 return false;
1895
1896 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1897 if ( authmethod )
1898 {
1899 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkRequest ) )
1900 {
1901 QgsDebugError( u"Network request updating not supported by authcfg: %1"_s.arg( authcfg ) );
1902 return true;
1903 }
1904
1905 if ( !authmethod->updateNetworkRequest( request, authcfg, dataprovider.toLower() ) )
1906 {
1907 authmethod->clearCachedConfig( authcfg );
1908 return false;
1909 }
1910 return true;
1911 }
1912 return false;
1913#else
1914 Q_UNUSED( request )
1915 Q_UNUSED( authcfg )
1916 Q_UNUSED( dataprovider )
1917 return false;
1918#endif
1919}
1920
1921bool QgsAuthManager::updateNetworkReply( QNetworkReply *reply, const QString &authcfg, const QString &dataprovider )
1922{
1923#ifdef HAVE_AUTH
1925
1926 if ( isDisabled() )
1927 return false;
1928
1929 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1930 if ( authmethod )
1931 {
1932 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkReply ) )
1933 {
1934 QgsDebugMsgLevel( u"Network reply updating not supported by authcfg: %1"_s.arg( authcfg ), 3 );
1935 return true;
1936 }
1937
1938 if ( !authmethod->updateNetworkReply( reply, authcfg, dataprovider.toLower() ) )
1939 {
1940 authmethod->clearCachedConfig( authcfg );
1941 return false;
1942 }
1943 return true;
1944 }
1945
1946 return false;
1947#else
1948 Q_UNUSED( reply )
1949 Q_UNUSED( authcfg )
1950 Q_UNUSED( dataprovider )
1951 return false;
1952#endif
1953}
1954
1955bool QgsAuthManager::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg, const QString &dataprovider )
1956{
1957#ifdef HAVE_AUTH
1959
1960 if ( isDisabled() )
1961 return false;
1962
1963 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1964 if ( authmethod )
1965 {
1966 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::DataSourceUri ) )
1967 {
1968 QgsDebugError( u"Data source URI updating not supported by authcfg: %1"_s.arg( authcfg ) );
1969 return true;
1970 }
1971
1972 if ( !authmethod->updateDataSourceUriItems( connectionItems, authcfg, dataprovider.toLower() ) )
1973 {
1974 authmethod->clearCachedConfig( authcfg );
1975 return false;
1976 }
1977 return true;
1978 }
1979
1980 return false;
1981#else
1982 Q_UNUSED( connectionItems )
1983 Q_UNUSED( authcfg )
1984 Q_UNUSED( dataprovider )
1985 return false;
1986#endif
1987}
1988
1989bool QgsAuthManager::updateNetworkProxy( QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider )
1990{
1991#ifdef HAVE_AUTH
1993
1994 if ( isDisabled() )
1995 return false;
1996
1997 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1998 if ( authmethod )
1999 {
2000 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkProxy ) )
2001 {
2002 QgsDebugError( u"Proxy updating not supported by authcfg: %1"_s.arg( authcfg ) );
2003 return true;
2004 }
2005
2006 if ( !authmethod->updateNetworkProxy( proxy, authcfg, dataprovider.toLower() ) )
2007 {
2008 authmethod->clearCachedConfig( authcfg );
2009 return false;
2010 }
2011 QgsDebugMsgLevel( u"Proxy updated successfully from authcfg: %1"_s.arg( authcfg ), 2 );
2012 return true;
2013 }
2014
2015 return false;
2016#else
2017 Q_UNUSED( proxy )
2018 Q_UNUSED( authcfg )
2019 Q_UNUSED( dataprovider )
2020 return false;
2021#endif
2022}
2023
2024bool QgsAuthManager::storeAuthSetting( const QString &key, const QVariant &value, bool encrypt )
2025{
2026#ifdef HAVE_AUTH
2028
2029 QMutexLocker locker( mMutex.get() );
2030 if ( key.isEmpty() )
2031 return false;
2032
2033 QString storeval( value.toString() );
2034 if ( encrypt )
2035 {
2036 if ( !setMasterPassword( true ) )
2037 {
2038 return false;
2039 }
2040 else
2041 {
2042 storeval = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), value.toString() );
2043 }
2044 }
2045
2046 if ( existsAuthSetting( key ) && !removeAuthSetting( key ) )
2047 {
2048 emit messageLog( tr( "Store setting: FAILED to remove pre-existing setting %1" ).arg( key ), authManTag(), Qgis::MessageLevel::Warning );
2049 return false;
2050 }
2051
2052 // Set the setting in the first storage that has the capability to store it
2053
2054 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateSetting ) )
2055 {
2056 if ( !defaultStorage->storeAuthSetting( key, storeval ) )
2057 {
2058 emit messageLog( tr( "Store setting: FAILED to store setting in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
2059 return false;
2060 }
2061 return true;
2062 }
2063 else
2064 {
2065 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2066 return false;
2067 }
2068#else
2069 Q_UNUSED( key )
2070 Q_UNUSED( value )
2071 Q_UNUSED( encrypt )
2072 return false;
2073#endif
2074}
2075
2076QVariant QgsAuthManager::authSetting( const QString &key, const QVariant &defaultValue, bool decrypt )
2077{
2078#ifdef HAVE_AUTH
2080
2081 QMutexLocker locker( mMutex.get() );
2082 if ( key.isEmpty() )
2083 return QVariant();
2084
2085 if ( decrypt && !setMasterPassword( true ) )
2086 return QVariant();
2087
2088 QVariant value = defaultValue;
2089
2090 // Loop through all storages with capability ReadSetting and get the setting from the first one that has the setting
2092
2093 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2094 {
2095 QString storeval = storage->loadAuthSetting( key );
2096 if ( !storeval.isEmpty() )
2097 {
2098 if ( decrypt )
2099 {
2100 storeval = QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), storeval );
2101 }
2102 value = storeval;
2103 break;
2104 }
2105 }
2106
2107 if ( storages.empty() )
2108 {
2109 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2110 }
2111
2112 return value;
2113#else
2114 Q_UNUSED( key )
2115 Q_UNUSED( defaultValue )
2116 Q_UNUSED( decrypt )
2117 return QVariant();
2118#endif
2119}
2120
2121bool QgsAuthManager::existsAuthSetting( const QString &key )
2122{
2123#ifdef HAVE_AUTH
2125
2126 QMutexLocker locker( mMutex.get() );
2127 if ( key.isEmpty() )
2128 return false;
2129
2130 // Loop through all storages with capability ReadSetting and get the setting from the first one that has the setting
2132
2133 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2134 {
2135 if ( storage->authSettingExists( key ) )
2136 {
2137 return true;
2138 }
2139 }
2140
2141 if ( storages.empty() )
2142 {
2143 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2144 }
2145
2146 return false;
2147#else
2148 Q_UNUSED( key )
2149 return false;
2150#endif
2151}
2152
2153bool QgsAuthManager::removeAuthSetting( const QString &key )
2154{
2155#ifdef HAVE_AUTH
2157
2158 QMutexLocker locker( mMutex.get() );
2159 if ( key.isEmpty() )
2160 return false;
2161
2162 // Loop through all storages with capability ReadSetting and delete from the first one that has the setting, fail if it has no capability
2164
2165 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2166 {
2167 if ( storage->authSettingExists( key ) )
2168 {
2170 {
2171 if ( !storage->removeAuthSetting( key ) )
2172 {
2173 emit messageLog( tr( "Remove setting: FAILED to remove setting from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2174 return false;
2175 }
2176 return true;
2177 }
2178 else
2179 {
2180 emit messageLog( tr( "Remove setting: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
2181 return false;
2182 }
2183 }
2184 }
2185
2186 if ( storages.empty() )
2187 {
2188 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2189 }
2190 return false;
2191#else
2192 Q_UNUSED( key )
2193 return false;
2194#endif
2195}
2196
2197#ifndef QT_NO_SSL
2198
2200
2202{
2203#ifdef HAVE_AUTH
2204 QgsScopedRuntimeProfile profile( "Initialize SSL cache" );
2205
2206 QMutexLocker locker( mMutex.get() );
2207 bool res = true;
2208 res = res && rebuildCaCertsCache();
2209 res = res && rebuildCertTrustCache();
2210 res = res && rebuildTrustedCaCertsCache();
2211 res = res && rebuildIgnoredSslErrorCache();
2212 mCustomConfigByHostCache.clear();
2213 mHasCheckedIfCustomConfigByHostExists = false;
2214
2215 if ( !res )
2216 QgsDebugError( u"Init of SSL caches FAILED"_s );
2217 return res;
2218#else
2219 return false;
2220#endif
2221}
2222
2223bool QgsAuthManager::storeCertIdentity( const QSslCertificate &cert, const QSslKey &key )
2224{
2225#ifdef HAVE_AUTH
2227
2228 QMutexLocker locker( mMutex.get() );
2229 if ( cert.isNull() )
2230 {
2231 QgsDebugError( u"Passed certificate is null"_s );
2232 return false;
2233 }
2234 if ( key.isNull() )
2235 {
2236 QgsDebugError( u"Passed private key is null"_s );
2237 return false;
2238 }
2239
2240 if ( !setMasterPassword( true ) )
2241 return false;
2242
2243 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2244
2245
2246 if ( existsCertIdentity( id ) && !removeCertIdentity( id ) )
2247 {
2248 QgsDebugError( u"Store certificate identity: FAILED to remove pre-existing certificate identity %1"_s.arg( id ) );
2249 return false;
2250 }
2251
2252 QString keypem( QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), key.toPem() ) );
2253
2255 {
2256 if ( !defaultStorage->storeCertIdentity( cert, keypem ) )
2257 {
2258 emit messageLog( tr( "Store certificate identity: FAILED to store certificate identity in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
2259 return false;
2260 }
2261 return true;
2262 }
2263 else
2264 {
2265 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2266 return false;
2267 }
2268#else
2269 Q_UNUSED( cert )
2270 Q_UNUSED( key )
2271 return false;
2272#endif
2273}
2274
2275const QSslCertificate QgsAuthManager::certIdentity( const QString &id )
2276{
2277#ifdef HAVE_AUTH
2279
2280 QMutexLocker locker( mMutex.get() );
2281
2282 QSslCertificate cert;
2283
2284 if ( id.isEmpty() )
2285 return cert;
2286
2287 // Loop through all storages with capability ReadCertificateIdentity and get the certificate from the first one that has the certificate
2289
2290 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2291 {
2292 cert = storage->loadCertIdentity( id );
2293 if ( !cert.isNull() )
2294 {
2295 return cert;
2296 }
2297 }
2298
2299 if ( storages.empty() )
2300 {
2301 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2302 }
2303
2304 return cert;
2305#else
2306 Q_UNUSED( id )
2307 return QSslCertificate();
2308#endif
2309}
2310
2311const QPair<QSslCertificate, QSslKey> QgsAuthManager::certIdentityBundle( const QString &id )
2312{
2314
2315 QMutexLocker locker( mMutex.get() );
2316 QPair<QSslCertificate, QSslKey> bundle;
2317 if ( id.isEmpty() )
2318 return bundle;
2319
2320 if ( !setMasterPassword( true ) )
2321 return bundle;
2322
2323 // Loop through all storages with capability ReadCertificateIdentity and get the certificate from the first one that has the certificate
2325
2326 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2327 {
2328 if ( storage->certIdentityExists( id ) )
2329 {
2330 QPair<QSslCertificate, QString> encryptedBundle { storage->loadCertIdentityBundle( id ) };
2331 if ( encryptedBundle.first.isNull() )
2332 {
2333 QgsDebugError( u"Certificate identity bundle is null for id: %1"_s.arg( id ) );
2334 return bundle;
2335 }
2336 QSslKey key( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), encryptedBundle.second ).toLatin1(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey );
2337 if ( key.isNull() )
2338 {
2339 QgsDebugError( u"Certificate identity bundle: FAILED to create private key"_s );
2340 return bundle;
2341 }
2342 bundle = qMakePair( encryptedBundle.first, key );
2343 break;
2344 }
2345 }
2346
2347 if ( storages.empty() )
2348 {
2349 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2350 return bundle;
2351 }
2352
2353 return bundle;
2354}
2355
2356const QStringList QgsAuthManager::certIdentityBundleToPem( const QString &id )
2357{
2358#ifdef HAVE_AUTH
2360
2361 QMutexLocker locker( mMutex.get() );
2362 QPair<QSslCertificate, QSslKey> bundle( certIdentityBundle( id ) );
2363 if ( QgsAuthCertUtils::certIsViable( bundle.first ) && !bundle.second.isNull() )
2364 {
2365 return QStringList() << QString( bundle.first.toPem() ) << QString( bundle.second.toPem() );
2366 }
2367 return QStringList();
2368#else
2369 Q_UNUSED( id )
2370 return QStringList();
2371#endif
2372}
2373
2374const QList<QSslCertificate> QgsAuthManager::certIdentities()
2375{
2376#ifdef HAVE_AUTH
2378
2379 QMutexLocker locker( mMutex.get() );
2380 QList<QSslCertificate> certs;
2381
2382 // Loop through all storages with capability ReadCertificateIdentity and collect the certificates from all storages
2384
2385 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2386 {
2387 const QList<QSslCertificate> storageCerts = storage->certIdentities();
2388 // Add if not already in the list, warn otherwise
2389 for ( const QSslCertificate &cert : std::as_const( storageCerts ) )
2390 {
2391 if ( !certs.contains( cert ) )
2392 {
2393 certs.append( cert );
2394 }
2395 else
2396 {
2397 emit messageLog( tr( "Certificate already in the list: %1" ).arg( cert.issuerDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
2398 }
2399 }
2400 }
2401
2402 if ( storages.empty() )
2403 {
2404 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2405 }
2406
2407 return certs;
2408#else
2409 return QList<QSslCertificate>();
2410#endif
2411}
2412
2414{
2415#ifdef HAVE_AUTH
2417
2418 QMutexLocker locker( mMutex.get() );
2419
2420 if ( isDisabled() )
2421 return {};
2422
2423 // Loop through all storages with capability ReadCertificateIdentity and collect the certificate ids from all storages
2425
2426 QStringList ids;
2427
2428 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2429 {
2430 const QStringList storageIds = storage->certIdentityIds();
2431 // Add if not already in the list, warn otherwise
2432 for ( const QString &id : std::as_const( storageIds ) )
2433 {
2434 if ( !ids.contains( id ) )
2435 {
2436 ids.append( id );
2437 }
2438 else
2439 {
2440 emit messageLog( tr( "Certificate identity id already in the list: %1" ).arg( id ), authManTag(), Qgis::MessageLevel::Warning );
2441 }
2442 }
2443 }
2444
2445 return ids;
2446#else
2447 return QStringList();
2448#endif
2449}
2450
2451bool QgsAuthManager::existsCertIdentity( const QString &id )
2452{
2453#ifdef HAVE_AUTH
2455
2456 QMutexLocker locker( mMutex.get() );
2457 if ( id.isEmpty() )
2458 return false;
2459
2460 // Loop through all storages with capability ReadCertificateIdentity and check if the certificate exists in any storage
2462
2463 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2464 {
2465 if ( storage->certIdentityExists( id ) )
2466 {
2467 return true;
2468 }
2469 }
2470
2471 if ( storages.empty() )
2472 {
2473 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2474 }
2475
2476 return false;
2477#else
2478 Q_UNUSED( id )
2479 return false;
2480#endif
2481}
2482
2483bool QgsAuthManager::removeCertIdentity( const QString &id )
2484{
2485#ifdef HAVE_AUTH
2487
2488 QMutexLocker locker( mMutex.get() );
2489 if ( id.isEmpty() )
2490 {
2491 QgsDebugError( u"Passed bundle ID is empty"_s );
2492 return false;
2493 }
2494
2495 // Loop through all storages with capability ReadCertificateIdentity and delete from the first one that has the bundle, fail if it has no capability
2497
2498 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2499 {
2500 if ( storage->certIdentityExists( id ) )
2501 {
2502 if ( !storage->removeCertIdentity( id ) )
2503 {
2504 emit messageLog( tr( "Remove certificate identity: FAILED to remove certificate identity from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2505 return false;
2506 }
2507 return true;
2508 }
2509 }
2510
2511 if ( storages.empty() )
2512 {
2513 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2514 }
2515
2516 return false;
2517
2518#else
2519 Q_UNUSED( id )
2520 return false;
2521#endif
2522}
2523
2525{
2526#ifdef HAVE_AUTH
2528
2529 QMutexLocker locker( mMutex.get() );
2530 if ( config.isNull() )
2531 {
2532 QgsDebugError( u"Passed config is null"_s );
2533 return false;
2534 }
2535
2536 const QSslCertificate cert( config.sslCertificate() );
2537 const QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2538
2539 if ( existsSslCertCustomConfig( id, config.sslHostPort() ) && !removeSslCertCustomConfig( id, config.sslHostPort() ) )
2540 {
2541 QgsDebugError( u"Store SSL certificate custom config: FAILED to remove pre-existing config %1"_s.arg( id ) );
2542 return false;
2543 }
2544
2546 {
2547 if ( !defaultStorage->storeSslCertCustomConfig( config ) )
2548 {
2549 emit messageLog( tr( "Store SSL certificate custom config: FAILED to store config in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
2550 return false;
2551 }
2552 }
2553 else
2554 {
2555 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2556 return false;
2557 }
2558
2560 mCustomConfigByHostCache.clear();
2561
2562 return true;
2563#else
2564 Q_UNUSED( config )
2565 return false;
2566#endif
2567}
2568
2569const QgsAuthConfigSslServer QgsAuthManager::sslCertCustomConfig( const QString &id, const QString &hostport )
2570{
2571#ifdef HAVE_AUTH
2573
2574 QMutexLocker locker( mMutex.get() );
2576
2577 if ( id.isEmpty() || hostport.isEmpty() )
2578 {
2579 QgsDebugError( u"Passed config ID or host:port is empty"_s );
2580 return config;
2581 }
2582
2583 // Loop through all storages with capability ReadSslCertificateCustomConfig and get the config from the first one that has the config
2585
2586 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2587 {
2588 if ( storage->sslCertCustomConfigExists( id, hostport ) )
2589 {
2590 config = storage->loadSslCertCustomConfig( id, hostport );
2591 if ( !config.isNull() )
2592 {
2593 return config;
2594 }
2595 else
2596 {
2597 emit messageLog( tr( "Could not load SSL custom config %1 %2 from the storage." ).arg( id, hostport ), authManTag(), Qgis::MessageLevel::Critical );
2598 return config;
2599 }
2600 }
2601 }
2602
2603 if ( storages.empty() )
2604 {
2605 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2606 }
2607
2608 return config;
2609
2610#else
2611 Q_UNUSED( id )
2612 Q_UNUSED( hostport )
2613 return QgsAuthConfigSslServer();
2614#endif
2615}
2616
2618{
2619#ifdef HAVE_AUTH
2621
2623 if ( hostport.isEmpty() )
2624 {
2625 return config;
2626 }
2627
2628 QMutexLocker locker( mMutex.get() );
2629
2630 if ( mCustomConfigByHostCache.contains( hostport ) )
2631 return mCustomConfigByHostCache.value( hostport );
2632
2633 // Loop through all storages with capability ReadSslCertificateCustomConfig and get the config from the first one that has the config
2635
2636 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2637 {
2638 config = storage->loadSslCertCustomConfigByHost( hostport );
2639 if ( !config.isNull() )
2640 {
2641 mCustomConfigByHostCache.insert( hostport, config );
2642 }
2643 }
2644
2645 if ( storages.empty() )
2646 {
2647 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2648 }
2649
2650 return config;
2651#else
2652 Q_UNUSED( hostport )
2653 return QgsAuthConfigSslServer();
2654#endif
2655}
2656
2657const QList<QgsAuthConfigSslServer> QgsAuthManager::sslCertCustomConfigs()
2658{
2659#ifdef HAVE_AUTH
2661
2662 QMutexLocker locker( mMutex.get() );
2663 QList<QgsAuthConfigSslServer> configs;
2664
2665 // Loop through all storages with capability ReadSslCertificateCustomConfig
2667
2668 QStringList ids;
2669
2670 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2671 {
2672 const QList<QgsAuthConfigSslServer> storageConfigs = storage->sslCertCustomConfigs();
2673 // Check if id + hostPort is not already in the list, warn otherwise
2674 for ( const auto &config : std::as_const( storageConfigs ) )
2675 {
2676 const QString id( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ) );
2677 const QString hostPort = config.sslHostPort();
2678 const QString shaHostPort( u"%1:%2"_s.arg( id, hostPort ) );
2679 if ( !ids.contains( shaHostPort ) )
2680 {
2681 ids.append( shaHostPort );
2682 configs.append( config );
2683 }
2684 else
2685 {
2686 emit messageLog( tr( "SSL custom config already in the list: %1" ).arg( hostPort ), authManTag(), Qgis::MessageLevel::Warning );
2687 }
2688 }
2689 }
2690
2691 if ( storages.empty() )
2692 {
2693 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2694 }
2695
2696 return configs;
2697#else
2698 return QList<QgsAuthConfigSslServer>();
2699#endif
2700}
2701
2702bool QgsAuthManager::existsSslCertCustomConfig( const QString &id, const QString &hostPort )
2703{
2704#ifdef HAVE_AUTH
2706
2707 QMutexLocker locker( mMutex.get() );
2708 if ( id.isEmpty() || hostPort.isEmpty() )
2709 {
2710 QgsDebugError( u"Passed config ID or host:port is empty"_s );
2711 return false;
2712 }
2713
2714 // Loop through all storages with capability ReadSslCertificateCustomConfig
2716
2717 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2718 {
2719 if ( storage->sslCertCustomConfigExists( id, hostPort ) )
2720 {
2721 return true;
2722 }
2723 }
2724
2725 if ( storages.empty() )
2726 {
2727 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2728 }
2729
2730 return false;
2731#else
2732 Q_UNUSED( id )
2733 Q_UNUSED( hostPort )
2734 return false;
2735#endif
2736}
2737
2738bool QgsAuthManager::removeSslCertCustomConfig( const QString &id, const QString &hostport )
2739{
2740#ifdef HAVE_AUTH
2742
2743 QMutexLocker locker( mMutex.get() );
2744 if ( id.isEmpty() || hostport.isEmpty() )
2745 {
2746 QgsDebugError( u"Passed config ID or host:port is empty"_s );
2747 return false;
2748 }
2749
2750 mCustomConfigByHostCache.clear();
2751
2752 // Loop through all storages with capability DeleteSslCertificateCustomConfig
2754
2755 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2756 {
2757 if ( storage->sslCertCustomConfigExists( id, hostport ) )
2758 {
2759 if ( !storage->removeSslCertCustomConfig( id, hostport ) )
2760 {
2761 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 );
2762 return false;
2763 }
2764 const QString shaHostPort( u"%1:%2"_s.arg( id, hostport ) );
2765 if ( mIgnoredSslErrorsCache.contains( shaHostPort ) )
2766 {
2767 mIgnoredSslErrorsCache.remove( shaHostPort );
2768 }
2769 return true;
2770 }
2771 }
2772
2773 if ( storages.empty() )
2774 {
2775 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2776 }
2777
2778 return false;
2779#else
2780 Q_UNUSED( id )
2781 Q_UNUSED( hostport )
2782 return false;
2783#endif
2784}
2785
2786
2788{
2789#ifdef HAVE_AUTH
2791
2792 QMutexLocker locker( mMutex.get() );
2793 if ( !mIgnoredSslErrorsCache.isEmpty() )
2794 {
2795 QgsDebugMsgLevel( u"Ignored SSL errors cache items:"_s, 1 );
2796 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = mIgnoredSslErrorsCache.constBegin();
2797 while ( i != mIgnoredSslErrorsCache.constEnd() )
2798 {
2799 QStringList errs;
2800 for ( auto err : i.value() )
2801 {
2802 errs << QgsAuthCertUtils::sslErrorEnumString( err );
2803 }
2804 QgsDebugMsgLevel( u"%1 = %2"_s.arg( i.key(), errs.join( ", " ) ), 1 );
2805 ++i;
2806 }
2807 }
2808 else
2809 {
2810 QgsDebugMsgLevel( u"Ignored SSL errors cache EMPTY"_s, 2 );
2811 }
2812#endif
2813}
2814
2816{
2817#ifdef HAVE_AUTH
2819
2820 QMutexLocker locker( mMutex.get() );
2821 if ( config.isNull() )
2822 {
2823 QgsDebugError( u"Passed config is null"_s );
2824 return false;
2825 }
2826
2827 QString shahostport( u"%1:%2"_s.arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ).trimmed(), config.sslHostPort().trimmed() ) );
2828 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2829 {
2830 mIgnoredSslErrorsCache.remove( shahostport );
2831 }
2832 const QList<QSslError::SslError> errenums( config.sslIgnoredErrorEnums() );
2833 if ( !errenums.isEmpty() )
2834 {
2835 mIgnoredSslErrorsCache.insert( shahostport, QSet<QSslError::SslError>( errenums.begin(), errenums.end() ) );
2836 QgsDebugMsgLevel( u"Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1"_s.arg( shahostport ), 2 );
2838 return true;
2839 }
2840
2841 QgsDebugMsgLevel( u"No ignored SSL errors to cache for sha:host:port = %1"_s.arg( shahostport ), 2 );
2842 return true;
2843#else
2844 Q_UNUSED( config )
2845 return false;
2846#endif
2847}
2848
2849bool QgsAuthManager::updateIgnoredSslErrorsCache( const QString &shahostport, const QList<QSslError> &errors )
2850{
2851#ifdef HAVE_AUTH
2853
2854 QMutexLocker locker( mMutex.get() );
2855 const thread_local QRegularExpression rx( QRegularExpression::anchoredPattern( "\\S+:\\S+:\\d+" ) );
2856 if ( !rx.match( shahostport ).hasMatch() )
2857 {
2859 "Passed shahostport does not match \\S+:\\S+:\\d+, "
2860 "e.g. 74a4ef5ea94512a43769b744cda0ca5049a72491:www.example.com:443"
2861 );
2862 return false;
2863 }
2864
2865 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2866 {
2867 mIgnoredSslErrorsCache.remove( shahostport );
2868 }
2869
2870 if ( errors.isEmpty() )
2871 {
2872 QgsDebugError( u"Passed errors list empty"_s );
2873 return false;
2874 }
2875
2876 QSet<QSslError::SslError> errs;
2877 for ( const auto &error : errors )
2878 {
2879 if ( error.error() == QSslError::NoError )
2880 continue;
2881
2882 errs.insert( error.error() );
2883 }
2884
2885 if ( errs.isEmpty() )
2886 {
2887 QgsDebugError( u"Passed errors list does not contain errors"_s );
2888 return false;
2889 }
2890
2891 mIgnoredSslErrorsCache.insert( shahostport, errs );
2892
2893 QgsDebugMsgLevel( u"Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1"_s.arg( shahostport ), 2 );
2895 return true;
2896#else
2897 Q_UNUSED( shahostport )
2898 Q_UNUSED( errors )
2899 return false;
2900#endif
2901}
2902
2904{
2905#ifdef HAVE_AUTH
2907
2908 QMutexLocker locker( mMutex.get() );
2909 QHash<QString, QSet<QSslError::SslError> > prevcache( mIgnoredSslErrorsCache );
2910 QHash<QString, QSet<QSslError::SslError> > nextcache;
2911
2912 // Loop through all storages with capability ReadSslCertificateCustomConfig
2914
2915 QStringList ids;
2916
2917 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2918 {
2919 const auto customConfigs { storage->sslCertCustomConfigs() };
2920 for ( const auto &config : std::as_const( customConfigs ) )
2921 {
2922 const QString shaHostPort( u"%1:%2"_s.arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ), config.sslHostPort() ) );
2923 if ( !ids.contains( shaHostPort ) )
2924 {
2925 ids.append( shaHostPort );
2926 if ( !config.sslIgnoredErrorEnums().isEmpty() )
2927 {
2928 nextcache.insert( shaHostPort, QSet<QSslError::SslError>( config.sslIgnoredErrorEnums().cbegin(), config.sslIgnoredErrorEnums().cend() ) );
2929 }
2930 if ( prevcache.contains( shaHostPort ) )
2931 {
2932 prevcache.remove( shaHostPort );
2933 }
2934 }
2935 else
2936 {
2937 emit messageLog( tr( "SSL custom config already in the list: %1" ).arg( config.sslHostPort() ), authManTag(), Qgis::MessageLevel::Warning );
2938 }
2939 }
2940 }
2941
2942 if ( !prevcache.isEmpty() )
2943 {
2944 // preserve any existing per-session ignored errors for hosts
2945 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = prevcache.constBegin();
2946 while ( i != prevcache.constEnd() )
2947 {
2948 nextcache.insert( i.key(), i.value() );
2949 ++i;
2950 }
2951 }
2952
2953 if ( nextcache != mIgnoredSslErrorsCache )
2954 {
2955 mIgnoredSslErrorsCache.clear();
2956 mIgnoredSslErrorsCache = nextcache;
2957 QgsDebugMsgLevel( u"Rebuild of ignored SSL errors cache SUCCEEDED"_s, 2 );
2959 return true;
2960 }
2961
2962 QgsDebugMsgLevel( u"Rebuild of ignored SSL errors cache SAME AS BEFORE"_s, 2 );
2964 return true;
2965#else
2966 return false;
2967#endif
2968}
2969
2970bool QgsAuthManager::storeCertAuthorities( const QList<QSslCertificate> &certs )
2971{
2972#ifdef HAVE_AUTH
2974
2975 QMutexLocker locker( mMutex.get() );
2976 if ( certs.isEmpty() )
2977 {
2978 QgsDebugError( u"Passed certificate list has no certs"_s );
2979 return false;
2980 }
2981
2982 for ( const auto &cert : certs )
2983 {
2984 if ( !storeCertAuthority( cert ) )
2985 return false;
2986 }
2987 return true;
2988#else
2989 Q_UNUSED( certs )
2990 return false;
2991#endif
2992}
2993
2994bool QgsAuthManager::storeCertAuthority( const QSslCertificate &cert )
2995{
2996#ifdef HAVE_AUTH
2998
2999 QMutexLocker locker( mMutex.get() );
3000 // don't refuse !cert.isValid() (actually just expired) CAs,
3001 // as user may want to ignore that SSL connection error
3002 if ( cert.isNull() )
3003 {
3004 QgsDebugError( u"Passed certificate is null"_s );
3005 return false;
3006 }
3007
3008 if ( existsCertAuthority( cert ) && !removeCertAuthority( cert ) )
3009 {
3010 QgsDebugError( u"Store certificate authority: FAILED to remove pre-existing certificate authority"_s );
3011 return false;
3012 }
3013
3015 {
3016 return defaultStorage->storeCertAuthority( cert );
3017 }
3018 else
3019 {
3020 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3021 return false;
3022 }
3023
3024 return false;
3025#else
3026 Q_UNUSED( cert )
3027 return false;
3028#endif
3029}
3030
3031const QSslCertificate QgsAuthManager::certAuthority( const QString &id )
3032{
3033#ifdef HAVE_AUTH
3035
3036 QMutexLocker locker( mMutex.get() );
3037 QSslCertificate emptycert;
3038 QSslCertificate cert;
3039 if ( id.isEmpty() )
3040 return emptycert;
3041
3042 // Loop through all storages with capability ReadCertificateAuthority and get the certificate from the first one that has the certificate
3044
3045 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3046 {
3047 cert = storage->loadCertAuthority( id );
3048 if ( !cert.isNull() )
3049 {
3050 return cert;
3051 }
3052 }
3053
3054 if ( storages.empty() )
3055 {
3056 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
3057 return emptycert;
3058 }
3059
3060 return cert;
3061#else
3062 Q_UNUSED( id )
3063 return QSslCertificate();
3064#endif
3065}
3066
3067bool QgsAuthManager::existsCertAuthority( const QSslCertificate &cert )
3068{
3069#ifdef HAVE_AUTH
3071
3072 QMutexLocker locker( mMutex.get() );
3073 if ( cert.isNull() )
3074 {
3075 QgsDebugError( u"Passed certificate is null"_s );
3076 return false;
3077 }
3078
3079 // Loop through all storages with capability ReadCertificateAuthority and get the certificate from the first one that has the certificate
3081
3082 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3083 {
3084 if ( storage->certAuthorityExists( cert ) )
3085 {
3086 return true;
3087 }
3088 }
3089
3090 if ( storages.empty() )
3091 {
3092 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
3093 }
3094
3095 return false;
3096#else
3097 return false;
3098#endif
3099}
3100
3101bool QgsAuthManager::removeCertAuthority( const QSslCertificate &cert )
3102{
3103#ifdef HAVE_AUTH
3105
3106 QMutexLocker locker( mMutex.get() );
3107 if ( cert.isNull() )
3108 {
3109 QgsDebugError( u"Passed certificate is null"_s );
3110 return false;
3111 }
3112
3113 // Loop through all storages with capability ReadCertificateAuthority and delete from the first one that has the certificate, fail if it has no capability
3115
3116 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3117 {
3118 if ( storage->certAuthorityExists( cert ) )
3119 {
3121 {
3122 emit messageLog( tr( "Remove certificate: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
3123 return false;
3124 }
3125
3126 if ( !storage->removeCertAuthority( cert ) )
3127 {
3128 emit messageLog( tr( "Remove certificate authority: FAILED to remove certificate authority from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
3129 return false;
3130 }
3131 return true;
3132 }
3133 }
3134
3135 if ( storages.empty() )
3136 {
3137 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3138 }
3139
3140 return false;
3141#else
3142 Q_UNUSED( cert )
3143 return false;
3144#endif
3145}
3146
3147const QList<QSslCertificate> QgsAuthManager::systemRootCAs()
3148{
3149#ifdef HAVE_AUTH
3150 return QSslConfiguration::systemCaCertificates();
3151#else
3152 return QList<QSslCertificate>();
3153#endif
3154}
3155
3156const QList<QSslCertificate> QgsAuthManager::extraFileCAs()
3157{
3158#ifdef HAVE_AUTH
3160
3161 QMutexLocker locker( mMutex.get() );
3162 QList<QSslCertificate> certs;
3163 QList<QSslCertificate> filecerts;
3164 QVariant cafileval = QgsAuthManager::instance()->authSetting( u"cafile"_s );
3165 if ( QgsVariantUtils::isNull( cafileval ) )
3166 return certs;
3167
3168 QVariant allowinvalid = QgsAuthManager::instance()->authSetting( u"cafileallowinvalid"_s, QVariant( false ) );
3169 if ( QgsVariantUtils::isNull( allowinvalid ) )
3170 return certs;
3171
3172 QString cafile( cafileval.toString() );
3173 if ( !cafile.isEmpty() && QFile::exists( cafile ) )
3174 {
3175 filecerts = QgsAuthCertUtils::certsFromFile( cafile );
3176 }
3177 // only CAs or certs capable of signing other certs are allowed
3178 for ( const auto &cert : std::as_const( filecerts ) )
3179 {
3180 if ( !allowinvalid.toBool() && ( cert.isBlacklisted() || cert.isNull() || cert.expiryDate() <= QDateTime::currentDateTime() || cert.effectiveDate() > QDateTime::currentDateTime() ) )
3181 {
3182 continue;
3183 }
3184
3185 if ( QgsAuthCertUtils::certificateIsAuthorityOrIssuer( cert ) )
3186 {
3187 certs << cert;
3188 }
3189 }
3190 return certs;
3191#else
3192 return QList<QSslCertificate>();
3193#endif
3194}
3195
3196const QList<QSslCertificate> QgsAuthManager::databaseCAs()
3197{
3198#ifdef HAVE_AUTH
3200
3201 QMutexLocker locker( mMutex.get() );
3202
3203 // Loop through all storages with capability ReadCertificateAuthority and collect the certificates from all storages
3205
3206 QList<QSslCertificate> certs;
3207
3208 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3209 {
3210 const QList<QSslCertificate> storageCerts = storage->caCerts();
3211 // Add if not already in the list, warn otherwise
3212 for ( const QSslCertificate &cert : std::as_const( storageCerts ) )
3213 {
3214 if ( !certs.contains( cert ) )
3215 {
3216 certs.append( cert );
3217 }
3218 else
3219 {
3220 emit messageLog( tr( "Certificate already in the list: %1" ).arg( cert.issuerDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
3221 }
3222 }
3223 }
3224
3225 if ( storages.empty() )
3226 {
3227 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3228 }
3229
3230 return certs;
3231#else
3232 return QList<QSslCertificate>();
3233#endif
3234}
3235
3236const QMap<QString, QSslCertificate> QgsAuthManager::mappedDatabaseCAs()
3237{
3239
3240 QMutexLocker locker( mMutex.get() );
3241 return QgsAuthCertUtils::mapDigestToCerts( databaseCAs() );
3242}
3243
3245{
3246#ifdef HAVE_AUTH
3248
3249 QMutexLocker locker( mMutex.get() );
3250 mCaCertsCache.clear();
3251 // in reverse order of precedence, with regards to duplicates, so QMap inserts overwrite
3252 insertCaCertInCache( QgsAuthCertUtils::SystemRoot, systemRootCAs() );
3253 insertCaCertInCache( QgsAuthCertUtils::FromFile, extraFileCAs() );
3254 insertCaCertInCache( QgsAuthCertUtils::InDatabase, databaseCAs() );
3255
3256 bool res = !mCaCertsCache.isEmpty(); // should at least contain system root CAs
3257 if ( !res )
3258 QgsDebugError( u"Rebuild of CA certs cache FAILED"_s );
3259 return res;
3260#else
3261 return false;
3262#endif
3263}
3264
3266{
3267#ifdef HAVE_AUTH
3269
3270 QMutexLocker locker( mMutex.get() );
3271 if ( cert.isNull() )
3272 {
3273 QgsDebugError( u"Passed certificate is null."_s );
3274 return false;
3275 }
3276
3277 if ( certTrustPolicy( cert ) == policy )
3278 {
3279 return true;
3280 }
3281
3283 {
3284 emit messageLog( tr( "Could not delete pre-existing certificate trust policy." ), authManTag(), Qgis::MessageLevel::Warning );
3285 return false;
3286 }
3287
3289 {
3290 return defaultStorage->storeCertTrustPolicy( cert, policy );
3291 }
3292 else
3293 {
3294 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3295 return false;
3296 }
3297#else
3298 Q_UNUSED( cert )
3299 Q_UNUSED( policy )
3300 return false;
3301#endif
3302}
3303
3305{
3306#ifdef HAVE_AUTH
3308
3309 QMutexLocker locker( mMutex.get() );
3310 if ( cert.isNull() )
3311 {
3312 QgsDebugError( u"Passed certificate is null"_s );
3314 }
3315
3316 // Loop through all storages with capability ReadCertificateTrustPolicy and get the policy from the first one that has the policy
3318
3319 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3320 {
3322 if ( policy != QgsAuthCertUtils::DefaultTrust )
3323 {
3324 return policy;
3325 }
3326 }
3327
3328 if ( storages.empty() )
3329 {
3330 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
3331 }
3332
3334#else
3335 Q_UNUSED( cert )
3337#endif
3338}
3339
3340bool QgsAuthManager::removeCertTrustPolicies( const QList<QSslCertificate> &certs )
3341{
3342#ifdef HAVE_AUTH
3344
3345 QMutexLocker locker( mMutex.get() );
3346 if ( certs.empty() )
3347 {
3348 QgsDebugError( u"Passed certificate list has no certs"_s );
3349 return false;
3350 }
3351
3352 for ( const auto &cert : certs )
3353 {
3354 if ( !removeCertTrustPolicy( cert ) )
3355 return false;
3356 }
3357 return true;
3358#else
3359 Q_UNUSED( certs )
3360 return false;
3361#endif
3362}
3363
3364bool QgsAuthManager::removeCertTrustPolicy( const QSslCertificate &cert )
3365{
3366#ifdef HAVE_AUTH
3368
3369 QMutexLocker locker( mMutex.get() );
3370 if ( cert.isNull() )
3371 {
3372 QgsDebugError( u"Passed certificate is null"_s );
3373 return false;
3374 }
3375
3376 // Loop through all storages with capability ReadCertificateTrustPolicy and delete from the first one that has the policy, fail if it has no capability
3378
3379 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3380 {
3381 if ( storage->certTrustPolicyExists( cert ) )
3382 {
3384 {
3385 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 );
3386 return false;
3387 }
3388
3389 if ( !storage->removeCertTrustPolicy( cert ) )
3390 {
3391 emit messageLog( tr( "Remove certificate trust policy: FAILED to remove certificate trust policy from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
3392 return false;
3393 }
3394 return true;
3395 }
3396 }
3397
3398 if ( storages.empty() )
3399 {
3400 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3401 }
3402
3403 return false;
3404#else
3405 Q_UNUSED( cert )
3406 return false;
3407#endif
3408}
3409
3411{
3412#ifdef HAVE_AUTH
3414
3415 QMutexLocker locker( mMutex.get() );
3416 if ( cert.isNull() )
3417 {
3419 }
3420
3421 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
3422 const QStringList &trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
3423 const QStringList &untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
3424
3426 if ( trustedids.contains( id ) )
3427 {
3429 }
3430 else if ( untrustedids.contains( id ) )
3431 {
3433 }
3434 return policy;
3435#else
3436 Q_UNUSED( cert )
3438#endif
3439}
3440
3442{
3443#ifdef HAVE_AUTH
3445
3446 if ( policy == QgsAuthCertUtils::DefaultTrust )
3447 {
3448 // set default trust policy to Trusted by removing setting
3449 return removeAuthSetting( u"certdefaulttrust"_s );
3450 }
3451 return storeAuthSetting( u"certdefaulttrust"_s, static_cast< int >( policy ) );
3452#else
3453 Q_UNUSED( policy )
3454 return false;
3455#endif
3456}
3457
3459{
3460#ifdef HAVE_AUTH
3462
3463 QMutexLocker locker( mMutex.get() );
3464 QVariant policy( authSetting( u"certdefaulttrust"_s ) );
3465 if ( QgsVariantUtils::isNull( policy ) )
3466 {
3468 }
3469 return static_cast< QgsAuthCertUtils::CertTrustPolicy >( policy.toInt() );
3470#else
3472#endif
3473}
3474
3476{
3477#ifdef HAVE_AUTH
3479
3480 QMutexLocker locker( mMutex.get() );
3481 mCertTrustCache.clear();
3482
3483 // Loop through all storages with capability ReadCertificateTrustPolicy
3485
3486 QStringList ids;
3487
3488 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3489 {
3490 const auto trustedCerts { storage->caCertsPolicy() };
3491 for ( auto it = trustedCerts.cbegin(); it != trustedCerts.cend(); ++it )
3492 {
3493 const QString id { it.key() };
3494 if ( !ids.contains( id ) )
3495 {
3496 ids.append( id );
3497 const QgsAuthCertUtils::CertTrustPolicy policy( it.value() );
3499 {
3500 QStringList ids;
3501 if ( mCertTrustCache.contains( QgsAuthCertUtils::Trusted ) )
3502 {
3503 ids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
3504 }
3505 mCertTrustCache.insert( QgsAuthCertUtils::Trusted, ids << it.key() );
3506 }
3507 }
3508 else
3509 {
3510 emit messageLog( tr( "Certificate already in the list: %1" ).arg( it.key() ), authManTag(), Qgis::MessageLevel::Warning );
3511 }
3512 }
3513 }
3514
3515 if ( !storages.empty() )
3516 {
3517 QgsDebugMsgLevel( u"Rebuild of cert trust policy cache SUCCEEDED"_s, 2 );
3518 return true;
3519 }
3520 else
3521 {
3522 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3523 return false;
3524 }
3525#else
3526 return false;
3527#endif
3528}
3529
3530const QList<QSslCertificate> QgsAuthManager::trustedCaCerts( bool includeinvalid )
3531{
3532#ifdef HAVE_AUTH
3534
3535 QMutexLocker locker( mMutex.get() );
3537 QStringList trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
3538 QStringList untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
3539 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
3540
3541 QList<QSslCertificate> trustedcerts;
3542 for ( int i = 0; i < certpairs.size(); ++i )
3543 {
3544 QSslCertificate cert( certpairs.at( i ).second );
3545 QString certid( QgsAuthCertUtils::shaHexForCert( cert ) );
3546 if ( trustedids.contains( certid ) )
3547 {
3548 // trusted certs are always added regardless of their validity
3549 trustedcerts.append( cert );
3550 }
3551 else if ( defaultpolicy == QgsAuthCertUtils::Trusted && !untrustedids.contains( certid ) )
3552 {
3553 if ( !includeinvalid && !QgsAuthCertUtils::certIsViable( cert ) )
3554 continue;
3555 trustedcerts.append( cert );
3556 }
3557 }
3558
3559 // update application default SSL config for new requests
3560 QSslConfiguration sslconfig( QSslConfiguration::defaultConfiguration() );
3561 sslconfig.setCaCertificates( trustedcerts );
3562 QSslConfiguration::setDefaultConfiguration( sslconfig );
3563
3564 return trustedcerts;
3565#else
3566 Q_UNUSED( includeinvalid )
3567 return QList<QSslCertificate>();
3568#endif
3569}
3570
3571const QList<QSslCertificate> QgsAuthManager::untrustedCaCerts( QList<QSslCertificate> trustedCAs )
3572{
3573#ifdef HAVE_AUTH
3575
3576 QMutexLocker locker( mMutex.get() );
3577 if ( trustedCAs.isEmpty() )
3578 {
3579 if ( mTrustedCaCertsCache.isEmpty() )
3580 {
3582 }
3583 trustedCAs = trustedCaCertsCache();
3584 }
3585
3586 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
3587
3588 QList<QSslCertificate> untrustedCAs;
3589 for ( int i = 0; i < certpairs.size(); ++i )
3590 {
3591 QSslCertificate cert( certpairs.at( i ).second );
3592 if ( !trustedCAs.contains( cert ) )
3593 {
3594 untrustedCAs.append( cert );
3595 }
3596 }
3597 return untrustedCAs;
3598#else
3599 Q_UNUSED( trustedCAs )
3600 return QList<QSslCertificate>();
3601#endif
3602}
3603
3605{
3606#ifdef HAVE_AUTH
3608
3609 QMutexLocker locker( mMutex.get() );
3610 mTrustedCaCertsCache = trustedCaCerts();
3611 QgsDebugMsgLevel( u"Rebuilt trusted cert authorities cache"_s, 2 );
3612 // TODO: add some error trapping for the operation
3613 return true;
3614#else
3615 return false;
3616#endif
3617}
3618
3620{
3621#ifdef HAVE_AUTH
3623
3624 QMutexLocker locker( mMutex.get() );
3625 return QgsAuthCertUtils::certsToPemText( trustedCaCertsCache() );
3626#else
3627 return QByteArray();
3628#endif
3629}
3630
3632{
3633#ifdef HAVE_AUTH
3635
3636 QMutexLocker locker( mMutex.get() );
3637 if ( masterPasswordIsSet() )
3638 {
3639 return passwordHelperWrite( mMasterPass );
3640 }
3641 return false;
3642#else
3643 return false;
3644#endif
3645}
3646
3648{
3649#ifdef HAVE_AUTH
3650 if ( !passwordHelperEnabled() )
3651 return false;
3652
3653 bool readOk = false;
3654 const QString currentPass = passwordHelperRead( readOk );
3655 if ( !readOk )
3656 return false;
3657
3658 if ( !currentPass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) )
3659 {
3660 return verifyMasterPassword( currentPass );
3661 }
3662 return false;
3663#else
3664 return false;
3665#endif
3666}
3667
3669{
3670#ifdef HAVE_AUTH
3671#if defined( Q_OS_MAC )
3672 return titleCase ? QObject::tr( "Keychain" ) : QObject::tr( "keychain" );
3673#elif defined( Q_OS_WIN )
3674 return titleCase ? QObject::tr( "Password Manager" ) : QObject::tr( "password manager" );
3675#elif defined( Q_OS_LINUX )
3676
3677 const QString desktopSession = qgetenv( "DESKTOP_SESSION" );
3678 const QString currentDesktop = qgetenv( "XDG_CURRENT_DESKTOP" );
3679 const QString gdmSession = qgetenv( "GDMSESSION" );
3680 // lets use a more precise string if we're running on KDE!
3681 if ( desktopSession.contains( "kde"_L1, Qt::CaseInsensitive ) || currentDesktop.contains( "kde"_L1, Qt::CaseInsensitive ) || gdmSession.contains( "kde"_L1, Qt::CaseInsensitive ) )
3682 {
3683 return titleCase ? QObject::tr( "Wallet" ) : QObject::tr( "wallet" );
3684 }
3685
3686 return titleCase ? QObject::tr( "Wallet/Key Ring" ) : QObject::tr( "wallet/key ring" );
3687#else
3688 return titleCase ? QObject::tr( "Password Manager" ) : QObject::tr( "password manager" );
3689#endif
3690#else
3691 Q_UNUSED( titleCase )
3692 return QString();
3693#endif
3694}
3695
3696
3698
3699#endif
3700
3702{
3703#ifdef HAVE_AUTH
3705
3706 if ( isDisabled() )
3707 return;
3708
3709 const QStringList ids = configIds();
3710 for ( const auto &authcfg : ids )
3711 {
3712 clearCachedConfig( authcfg );
3713 }
3714#endif
3715}
3716
3717void QgsAuthManager::clearCachedConfig( const QString &authcfg )
3718{
3719#ifdef HAVE_AUTH
3721
3722 if ( isDisabled() )
3723 return;
3724
3725 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
3726 if ( authmethod )
3727 {
3728 authmethod->clearCachedConfig( authcfg );
3729 }
3730#else
3731 Q_UNUSED( authcfg )
3732#endif
3733}
3734
3735void QgsAuthManager::writeToConsole( const QString &message, const QString &tag, Qgis::MessageLevel level )
3736{
3737#ifdef HAVE_AUTH
3738 Q_UNUSED( tag )
3739
3741
3742 // only output WARNING and CRITICAL messages
3743 if ( level == Qgis::MessageLevel::Info )
3744 return;
3745
3746 QString msg;
3747 switch ( level )
3748 {
3750 msg += "WARNING: "_L1;
3751 break;
3753 msg += "ERROR: "_L1;
3754 break;
3755 default:
3756 break;
3757 }
3758 msg += message;
3759
3760 QTextStream out( stdout, QIODevice::WriteOnly );
3761 out << msg << Qt::endl;
3762#else
3763 Q_UNUSED( message )
3764 Q_UNUSED( tag )
3765 Q_UNUSED( level )
3766#endif
3767}
3768
3769void QgsAuthManager::tryToStartDbErase()
3770{
3771#ifdef HAVE_AUTH
3773
3774 ++mScheduledDbEraseRequestCount;
3775 // wait a total of 90 seconds for GUI availiability or user interaction, then cancel schedule
3776 int trycutoff = 90 / ( mScheduledDbEraseRequestWait ? mScheduledDbEraseRequestWait : 3 );
3777 if ( mScheduledDbEraseRequestCount >= trycutoff )
3778 {
3780 QgsDebugMsgLevel( u"authDatabaseEraseRequest emitting/scheduling canceled"_s, 2 );
3781 return;
3782 }
3783 else
3784 {
3785 QgsDebugMsgLevel( u"authDatabaseEraseRequest attempt (%1 of %2)"_s.arg( mScheduledDbEraseRequestCount ).arg( trycutoff ), 2 );
3786 }
3787
3788 if ( scheduledAuthDatabaseErase() && !mScheduledDbEraseRequestEmitted && mMutex->tryLock() )
3789 {
3790 // see note in header about this signal's use
3791 mScheduledDbEraseRequestEmitted = true;
3793
3794 mMutex->unlock();
3795
3796 QgsDebugMsgLevel( u"authDatabaseEraseRequest emitted"_s, 2 );
3797 return;
3798 }
3799 QgsDebugMsgLevel( u"authDatabaseEraseRequest emit skipped"_s, 2 );
3800#endif
3801}
3802
3803
3805{
3806#ifdef HAVE_AUTH
3807 QMutexLocker locker( mMutex.get() );
3808
3809 QMapIterator<QThread *, QMetaObject::Connection> iterator( mConnectedThreads );
3810 while ( iterator.hasNext() )
3811 {
3812 iterator.next();
3813 QThread::disconnect( iterator.value() );
3814 }
3815
3816 if ( !mAuthInit )
3817 return;
3818
3819 locker.unlock();
3820
3821 if ( !isDisabled() )
3822 {
3824 qDeleteAll( mAuthMethods );
3825
3827 QSqlDatabase authConn = authDatabaseConnection();
3829 if ( authConn.isValid() && authConn.isOpen() )
3830 authConn.close();
3831 }
3832
3833 QSqlDatabase::removeDatabase( u"authentication.configs"_s );
3834#endif
3835}
3836
3838{
3839 QMutexLocker locker( mMutex.get() );
3840 if ( !mAuthConfigurationStorageRegistry )
3841 {
3842 mAuthConfigurationStorageRegistry = std::make_unique<QgsAuthConfigurationStorageRegistry>();
3843 }
3844 return mAuthConfigurationStorageRegistry.get();
3845}
3846
3847
3848QString QgsAuthManager::passwordHelperName() const
3849{
3850#ifdef HAVE_AUTH
3851 return tr( "Password Helper" );
3852#else
3853 return QString();
3854#endif
3855}
3856
3857
3858void QgsAuthManager::passwordHelperLog( const QString &msg ) const
3859{
3860#ifdef HAVE_AUTH
3862
3864 {
3865 QgsMessageLog::logMessage( msg, passwordHelperName() );
3866 }
3867#else
3868 Q_UNUSED( msg )
3869#endif
3870}
3871
3873{
3874#ifdef HAVE_AUTH
3876
3877 passwordHelperLog( tr( "Opening %1 for DELETE…" ).arg( passwordHelperDisplayName() ) );
3878 bool result;
3879 QKeychain::DeletePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3880 job.setInsecureFallback( settingsPasswordHelperInsecureFallback->value() );
3881 job.setAutoDelete( false );
3882 job.setKey( authPasswordHelperKeyName() );
3883 QEventLoop loop;
3884 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3885 job.start();
3886 loop.exec();
3887 if ( job.error() )
3888 {
3889 mPasswordHelperErrorCode = job.error();
3890 mPasswordHelperErrorMessage = tr( "Delete password failed: %1." ).arg( job.errorString() );
3891 // Signals used in the tests to exit main application loop
3892 emit passwordHelperFailure();
3893 result = false;
3894 }
3895 else
3896 {
3897 // Signals used in the tests to exit main application loop
3898 emit passwordHelperSuccess();
3899 result = true;
3900 }
3901 passwordHelperProcessError();
3902 return result;
3903#else
3904 return false;
3905#endif
3906}
3907
3908QString QgsAuthManager::passwordHelperRead( bool &ok )
3909{
3910#ifdef HAVE_AUTH
3911 ok = false;
3913
3914 // Retrieve it!
3915 QString password;
3916 passwordHelperLog( tr( "Opening %1 for READ…" ).arg( passwordHelperDisplayName() ) );
3917 QKeychain::ReadPasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3918 job.setInsecureFallback( settingsPasswordHelperInsecureFallback->value() );
3919 job.setAutoDelete( false );
3920 job.setKey( authPasswordHelperKeyName() );
3921 QEventLoop loop;
3922 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3923 job.start();
3924 loop.exec();
3925 if ( job.error() )
3926 {
3927 mPasswordHelperErrorCode = job.error();
3928 mPasswordHelperErrorMessage = tr( "Retrieving password from the %1 failed: %2." ).arg( passwordHelperDisplayName(), job.errorString() );
3929 // Signals used in the tests to exit main application loop
3930 emit passwordHelperFailure();
3931 }
3932 else
3933 {
3934 password = job.textData();
3935 // Password is there but it is empty, treat it like if it was not found
3936 if ( password.isEmpty() )
3937 {
3938 mPasswordHelperErrorCode = QKeychain::EntryNotFound;
3939 mPasswordHelperErrorMessage = tr( "Empty password retrieved from the %1." ).arg( passwordHelperDisplayName( true ) );
3940 // Signals used in the tests to exit main application loop
3941 emit passwordHelperFailure();
3942 }
3943 else
3944 {
3945 ok = true;
3946 // Signals used in the tests to exit main application loop
3947 emit passwordHelperSuccess();
3948 }
3949 }
3950 passwordHelperProcessError();
3951 return password;
3952#else
3953 Q_UNUSED( ok )
3954 return QString();
3955#endif
3956}
3957
3958bool QgsAuthManager::passwordHelperWrite( const QString &password )
3959{
3960#ifdef HAVE_AUTH
3962
3963 Q_ASSERT( !password.isEmpty() );
3964 bool result;
3965 passwordHelperLog( tr( "Opening %1 for WRITE…" ).arg( passwordHelperDisplayName() ) );
3966 QKeychain::WritePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3967 job.setInsecureFallback( settingsPasswordHelperInsecureFallback->value() );
3968 job.setAutoDelete( false );
3969 job.setKey( authPasswordHelperKeyName() );
3970 job.setTextData( password );
3971 QEventLoop loop;
3972 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3973 job.start();
3974 loop.exec();
3975 if ( job.error() )
3976 {
3977 mPasswordHelperErrorCode = job.error();
3978 mPasswordHelperErrorMessage = tr( "Storing password in the %1 failed: %2." ).arg( passwordHelperDisplayName(), job.errorString() );
3979 // Signals used in the tests to exit main application loop
3980 emit passwordHelperFailure();
3981 result = false;
3982 }
3983 else
3984 {
3985 passwordHelperClearErrors();
3986 // Signals used in the tests to exit main application loop
3987 emit passwordHelperSuccess();
3988 result = true;
3989 }
3990 passwordHelperProcessError();
3991 return result;
3992#else
3993 Q_UNUSED( password )
3994 return false;
3995#endif
3996}
3997
3999{
4000#ifdef HAVE_AUTH
4001 // Does the user want to store the password in the wallet?
4002 return settingsUsePasswordHelper->value();
4003#else
4004 return false;
4005#endif
4006}
4007
4009{
4010#ifdef HAVE_AUTH
4011 settingsUsePasswordHelper->setValue( enabled );
4012 emit messageLog(
4013 enabled ? tr( "Your %1 will be <b>used from now</b> on to store and retrieve the master password." ).arg( passwordHelperDisplayName() )
4014 : tr( "Your %1 will <b>not be used anymore</b> to store and retrieve the master password." ).arg( passwordHelperDisplayName() )
4015 );
4016#else
4017 Q_UNUSED( enabled )
4018#endif
4019}
4020
4022{
4023#ifdef HAVE_AUTH
4024 // Does the user want to store the password in the wallet?
4025 return settingsPasswordHelperLogging->value();
4026#else
4027 return false;
4028#endif
4029}
4030
4032{
4033#ifdef HAVE_AUTH
4034 settingsPasswordHelperLogging->setValue( enabled );
4035#else
4036 Q_UNUSED( enabled )
4037#endif
4038}
4039
4040void QgsAuthManager::passwordHelperClearErrors()
4041{
4042#ifdef HAVE_AUTH
4043 mPasswordHelperErrorCode = QKeychain::NoError;
4044 mPasswordHelperErrorMessage.clear();
4045#endif
4046}
4047
4048void QgsAuthManager::passwordHelperProcessError()
4049{
4050#ifdef HAVE_AUTH
4052
4053 if ( mPasswordHelperErrorCode == QKeychain::AccessDenied
4054 || mPasswordHelperErrorCode == QKeychain::AccessDeniedByUser
4055 || mPasswordHelperErrorCode == QKeychain::NoBackendAvailable
4056 || mPasswordHelperErrorCode == QKeychain::NotImplemented )
4057 {
4058 // If the error is permanent or the user denied access to the wallet
4059 // we also want to disable the wallet system to prevent annoying
4060 // notification on each subsequent access.
4061 setPasswordHelperEnabled( false );
4062 mPasswordHelperErrorMessage = tr(
4063 "There was an error and integration with your %1 has been disabled. "
4064 "You can re-enable it at any time through the \"Utilities\" menu "
4065 "in the Authentication pane of the options dialog. %2"
4066 )
4067 .arg( passwordHelperDisplayName(), mPasswordHelperErrorMessage );
4068 }
4069 if ( mPasswordHelperErrorCode != QKeychain::NoError )
4070 {
4071 // We've got an error from the wallet
4072 passwordHelperLog( tr( "Error in %1: %2" ).arg( passwordHelperDisplayName(), mPasswordHelperErrorMessage ) );
4073 emit passwordHelperMessageLog( mPasswordHelperErrorMessage, authManTag(), Qgis::MessageLevel::Critical );
4074 }
4075 passwordHelperClearErrors();
4076#endif
4077}
4078
4079
4080bool QgsAuthManager::masterPasswordInput()
4081{
4082#ifdef HAVE_AUTH
4084
4085 if ( isDisabled() )
4086 return false;
4087
4088 QString pass;
4089 bool storedPasswordIsValid = false;
4090 bool ok = false;
4091
4092 // Read the password from the wallet
4093 if ( passwordHelperEnabled() )
4094 {
4095 bool readOk = false;
4096 pass = passwordHelperRead( readOk );
4097 if ( readOk && !pass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) )
4098 {
4099 // Let's check the password!
4100 if ( verifyMasterPassword( pass ) )
4101 {
4102 ok = true;
4103 storedPasswordIsValid = true;
4104 }
4105 else
4106 {
4107 emit passwordHelperMessageLog( tr( "Master password stored in the %1 is not valid" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
4108 }
4109 }
4110 }
4111
4112 if ( !ok )
4113 {
4114 pass.clear();
4116 }
4117
4118 if ( ok && !pass.isEmpty() && mMasterPass != pass )
4119 {
4120 mMasterPass = pass;
4121 if ( passwordHelperEnabled() && !storedPasswordIsValid )
4122 {
4123 if ( !passwordHelperWrite( pass ) )
4124 {
4125 emit passwordHelperMessageLog( tr( "Master password could not be written to the %1" ).arg( passwordHelperDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
4126 }
4127 }
4128 return true;
4129 }
4130 return false;
4131#else
4132 return false;
4133#endif
4134}
4135
4136bool QgsAuthManager::masterPasswordRowsInDb( int &rows ) const
4137{
4138#ifdef HAVE_AUTH
4139 bool res = false;
4141
4142 if ( isDisabled() )
4143 return res;
4144
4145 rows = 0;
4146
4147 QMutexLocker locker( mMutex.get() );
4148
4149 // Loop through all storages with capability ReadMasterPassword and count the number of master passwords
4151
4152 if ( storages.empty() )
4153 {
4154 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
4155 }
4156 else
4157 {
4158 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4159 {
4160 try
4161 {
4162 rows += storage->masterPasswords().count();
4163 // if we successfully queuried at least one storage, the result from this function must be true
4164 res = true;
4165 }
4166 catch ( const QgsNotSupportedException &e )
4167 {
4168 // It should not happen because we are checking the capability in advance
4170 }
4171 }
4172 }
4173
4174 return res;
4175#else
4176 Q_UNUSED( rows )
4177 return false;
4178#endif
4179}
4180
4182{
4183#ifdef HAVE_AUTH
4185
4186 if ( isDisabled() )
4187 return false;
4188
4189 int rows = 0;
4190 if ( !masterPasswordRowsInDb( rows ) )
4191 {
4192 const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
4193 QgsDebugError( err );
4195
4196 return false;
4197 }
4198 return ( rows == 1 );
4199#else
4200 return false;
4201#endif
4202}
4203
4204bool QgsAuthManager::masterPasswordCheckAgainstDb( const QString &compare ) const
4205{
4206#ifdef HAVE_AUTH
4208
4209 if ( isDisabled() )
4210 return false;
4211
4212 // Only check the default DB
4213 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ReadMasterPassword ) )
4214 {
4215 try
4216 {
4217 const QList<QgsAuthConfigurationStorage::MasterPasswordConfig> passwords { defaultStorage->masterPasswords() };
4218 if ( passwords.size() == 0 )
4219 {
4220 emit messageLog( tr( "Master password: FAILED to access database" ), authManTag(), Qgis::MessageLevel::Critical );
4221 return false;
4222 }
4223 const QgsAuthConfigurationStorage::MasterPasswordConfig storedPassword { passwords.first() };
4224 return QgsAuthCrypto::verifyPasswordKeyHash( compare.isNull() ? mMasterPass : compare, storedPassword.salt, storedPassword.hash );
4225 }
4226 catch ( const QgsNotSupportedException &e )
4227 {
4228 // It should not happen because we are checking the capability in advance
4230 return false;
4231 }
4232 }
4233 else
4234 {
4235 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
4236 return false;
4237 }
4238#else
4239 Q_UNUSED( compare )
4240 return false;
4241#endif
4242}
4243
4244bool QgsAuthManager::masterPasswordStoreInDb() const
4245{
4246#ifdef HAVE_AUTH
4248
4249 if ( isDisabled() )
4250 return false;
4251
4252 QString salt, hash, civ;
4253 QgsAuthCrypto::passwordKeyHash( mMasterPass, &salt, &hash, &civ );
4254
4255 // Only store in the default DB
4256 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateMasterPassword ) )
4257 {
4258 try
4259 {
4260 return defaultStorage->storeMasterPassword( { salt, civ, hash } );
4261 }
4262 catch ( const QgsNotSupportedException &e )
4263 {
4264 // It should not happen because we are checking the capability in advance
4266 return false;
4267 }
4268 }
4269 else
4270 {
4271 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
4272 return false;
4273 }
4274#else
4275 return false;
4276#endif
4277}
4278
4279bool QgsAuthManager::masterPasswordClearDb()
4280{
4281#ifdef HAVE_AUTH
4283
4284 if ( isDisabled() )
4285 return false;
4286
4287 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::DeleteMasterPassword ) )
4288 {
4289 try
4290 {
4291 return defaultStorage->clearMasterPasswords();
4292 }
4293 catch ( const QgsNotSupportedException &e )
4294 {
4295 // It should not happen because we are checking the capability in advance
4297 return false;
4298 }
4299 }
4300 else
4301 {
4302 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
4303 return false;
4304 }
4305#else
4306 return false;
4307#endif
4308}
4309
4310const QString QgsAuthManager::masterPasswordCiv() const
4311{
4312#ifdef HAVE_AUTH
4314
4315 if ( isDisabled() )
4316 return QString();
4317
4318 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ReadMasterPassword ) )
4319 {
4320 try
4321 {
4322 const QList<QgsAuthConfigurationStorage::MasterPasswordConfig> passwords { defaultStorage->masterPasswords() };
4323 if ( passwords.size() == 0 )
4324 {
4325 emit messageLog( tr( "Master password: FAILED to access database" ), authManTag(), Qgis::MessageLevel::Critical );
4326 return QString();
4327 }
4328 return passwords.first().civ;
4329 }
4330 catch ( const QgsNotSupportedException &e )
4331 {
4332 // It should not happen because we are checking the capability in advance
4334 return QString();
4335 }
4336 }
4337 else
4338 {
4339 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
4340 return QString();
4341 }
4342#else
4343 return QString();
4344#endif
4345}
4346
4347QStringList QgsAuthManager::configIds() const
4348{
4349#ifdef HAVE_AUTH
4351
4352 QStringList configKeys = QStringList();
4353
4354 if ( isDisabled() )
4355 return configKeys;
4356
4357 // Loop through all storages with capability ReadConfiguration and get the config ids
4359
4360 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4361 {
4362 try
4363 {
4364 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
4365 // Check if the config ids are already in the list
4366 for ( auto it = configs.cbegin(); it != configs.cend(); ++it )
4367 {
4368 if ( !configKeys.contains( it.key() ) )
4369 {
4370 configKeys.append( it.key() );
4371 }
4372 else
4373 {
4374 emit messageLog( tr( "Config id %1 is already in the list" ).arg( it.key() ), authManTag(), Qgis::MessageLevel::Warning );
4375 }
4376 }
4377 }
4378 catch ( const QgsNotSupportedException &e )
4379 {
4380 // It should not happen because we are checking the capability in advance
4382 }
4383 }
4384
4385 return configKeys;
4386#else
4387 return QStringList();
4388#endif
4389}
4390
4391bool QgsAuthManager::verifyPasswordCanDecryptConfigs() const
4392{
4393#ifdef HAVE_AUTH
4395
4396 if ( isDisabled() )
4397 return false;
4398
4399 // no need to check for setMasterPassword, since this is private and it will be set
4400
4401 // Loop through all storages with capability ReadConfiguration and check if the password can decrypt the configs
4403
4404 for ( const QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4405 {
4406 if ( !storage->isEncrypted() )
4407 {
4408 continue;
4409 }
4410
4411 try
4412 {
4413 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigsWithPayload();
4414 for ( auto it = configs.cbegin(); it != configs.cend(); ++it )
4415 {
4416 QString configstring( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), it.value().config( u"encrypted_payload"_s ) ) );
4417 if ( configstring.isEmpty() )
4418 {
4419 QgsDebugError( u"Verify password can decrypt configs FAILED, could not decrypt a config (id: %1) from storage %2"_s.arg( it.key(), storage->name() ) );
4420 return false;
4421 }
4422 }
4423 }
4424 catch ( const QgsNotSupportedException &e )
4425 {
4426 // It should not happen because we are checking the capability in advance
4428 return false;
4429 }
4430 }
4431
4432 if ( storages.empty() )
4433 {
4434 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
4435 return false;
4436 }
4437
4438 return true;
4439#else
4440 return false;
4441#endif
4442}
4443
4444bool QgsAuthManager::reencryptAllAuthenticationConfigs( const QString &prevpass, const QString &prevciv )
4445{
4446#ifdef HAVE_AUTH
4448
4449 if ( isDisabled() )
4450 return false;
4451
4452 bool res = true;
4453 const QStringList ids = configIds();
4454 for ( const auto &configid : ids )
4455 {
4456 res = res && reencryptAuthenticationConfig( configid, prevpass, prevciv );
4457 }
4458 return res;
4459#else
4460 Q_UNUSED( prevpass )
4461 Q_UNUSED( prevciv )
4462 return false;
4463#endif
4464}
4465
4466bool QgsAuthManager::reencryptAuthenticationConfig( const QString &authcfg, const QString &prevpass, const QString &prevciv )
4467{
4468#ifdef HAVE_AUTH
4470
4471 if ( isDisabled() )
4472 return false;
4473
4474 // no need to check for setMasterPassword, since this is private and it will be set
4475
4476 // Loop through all storages with capability ReadConfiguration and reencrypt the config
4478
4479 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4480 {
4481 try
4482 {
4483 if ( storage->methodConfigExists( authcfg ) )
4484 {
4485 if ( !storage->isEncrypted() )
4486 {
4487 return true;
4488 }
4489
4490 QString payload;
4491 const QgsAuthMethodConfig config = storage->loadMethodConfig( authcfg, payload, true );
4492 if ( payload.isEmpty() || !config.isValid( true ) )
4493 {
4494 QgsDebugError( u"Reencrypt FAILED, could not find config (id: %1)"_s.arg( authcfg ) );
4495 return false;
4496 }
4497
4498 QString configstring( QgsAuthCrypto::decrypt( prevpass, prevciv, payload ) );
4499 if ( configstring.isEmpty() )
4500 {
4501 QgsDebugError( u"Reencrypt FAILED, could not decrypt config (id: %1)"_s.arg( authcfg ) );
4502 return false;
4503 }
4504
4505 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
4506
4507 if ( !storage->storeMethodConfig( config, configstring ) )
4508 {
4509 emit messageLog( tr( "Store config: FAILED to store config in default storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
4510 return false;
4511 }
4512 return true;
4513 }
4514 }
4515 catch ( const QgsNotSupportedException &e )
4516 {
4517 // It should not happen because we are checking the capability in advance
4519 return false;
4520 }
4521 }
4522
4523 if ( storages.empty() )
4524 {
4525 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
4526 }
4527 else
4528 {
4529 emit messageLog( tr( "Reencrypt FAILED, could not find config (id: %1)" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
4530 }
4531
4532 return false;
4533#else
4534 Q_UNUSED( authcfg )
4535 Q_UNUSED( prevpass )
4536 Q_UNUSED( prevciv )
4537 return false;
4538#endif
4539}
4540
4541bool QgsAuthManager::reencryptAllAuthenticationSettings( const QString &prevpass, const QString &prevciv )
4542{
4544
4545 // TODO: start remove (when function is actually used)
4546 Q_UNUSED( prevpass )
4547 Q_UNUSED( prevciv )
4548 return true;
4549 // end remove
4550
4551#if 0
4552 if ( isDisabled() )
4553 return false;
4554
4556 // When adding settings that require encryption, add to list //
4558
4559 QStringList encryptedsettings;
4560 encryptedsettings << "";
4561
4562 for ( const auto & sett, std::as_const( encryptedsettings ) )
4563 {
4564 if ( sett.isEmpty() || !existsAuthSetting( sett ) )
4565 continue;
4566
4567 // no need to check for setMasterPassword, since this is private and it will be set
4568
4569 QSqlQuery query( authDbConnection() );
4570
4571 query.prepare( QStringLiteral( "SELECT value FROM %1 "
4572 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
4573
4574 query.bindValue( ":setting", sett );
4575
4576 if ( !authDbQuery( &query ) )
4577 return false;
4578
4579 if ( !query.isActive() || !query.isSelect() )
4580 {
4581 QgsDebugError( u"Reencrypt FAILED, query not active or a select operation for setting: %2"_s.arg( sett ) );
4582 return false;
4583 }
4584
4585 if ( query.first() )
4586 {
4587 QString settvalue( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
4588
4589 query.clear();
4590
4591 query.prepare( QStringLiteral( "UPDATE %1 "
4592 "SET value = :value "
4593 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
4594
4595 query.bindValue( ":setting", sett );
4596 query.bindValue( ":value", QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), settvalue ) );
4597
4598 if ( !authDbStartTransaction() )
4599 return false;
4600
4601 if ( !authDbQuery( &query ) )
4602 return false;
4603
4604 if ( !authDbCommit() )
4605 return false;
4606
4607 QgsDebugMsgLevel( u"Reencrypt SUCCESS for setting: %2"_s.arg( sett ), 2 );
4608 return true;
4609 }
4610 else
4611 {
4612 QgsDebugError( u"Reencrypt FAILED, could not find in db setting: %2"_s.arg( sett ) );
4613 return false;
4614 }
4615
4616 if ( query.next() )
4617 {
4618 QgsDebugError( u"Select contains more than one for setting: %1"_s.arg( sett ) );
4619 emit messageOut( tr( "Authentication database contains duplicate setting keys" ), authManTag(), WARNING );
4620 }
4621
4622 return false;
4623 }
4624
4625 return true;
4626#endif
4627}
4628
4629bool QgsAuthManager::reencryptAllAuthenticationIdentities( const QString &prevpass, const QString &prevciv )
4630{
4631#ifdef HAVE_AUTH
4633
4634 if ( isDisabled() )
4635 return false;
4636
4637 bool res = true;
4638 const QStringList ids = certIdentityIds();
4639 for ( const auto &identid : ids )
4640 {
4641 res = res && reencryptAuthenticationIdentity( identid, prevpass, prevciv );
4642 }
4643 return res;
4644#else
4645 Q_UNUSED( prevpass )
4646 Q_UNUSED( prevciv )
4647 return false;
4648#endif
4649}
4650
4651bool QgsAuthManager::reencryptAuthenticationIdentity( const QString &identid, const QString &prevpass, const QString &prevciv )
4652{
4653#ifdef HAVE_AUTH
4655
4656 if ( isDisabled() )
4657 return false;
4658
4659 // no need to check for setMasterPassword, since this is private and it will be set
4660
4661 // Loop through all storages with capability ReadCertificateIdentity and reencrypt the identity
4663
4664
4665 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4666 {
4667 try
4668 {
4669 if ( storage->certIdentityExists( identid ) )
4670 {
4671 if ( !storage->isEncrypted() )
4672 {
4673 return true;
4674 }
4675
4676 const QPair<QSslCertificate, QString> identityBundle = storage->loadCertIdentityBundle( identid );
4677 QString keystring( QgsAuthCrypto::decrypt( prevpass, prevciv, identityBundle.second ) );
4678 if ( keystring.isEmpty() )
4679 {
4680 QgsDebugError( u"Reencrypt FAILED, could not decrypt identity id: %1"_s.arg( identid ) );
4681 return false;
4682 }
4683
4684 keystring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), keystring );
4685 return storage->storeCertIdentity( identityBundle.first, keystring );
4686 }
4687 }
4688 catch ( const QgsNotSupportedException &e )
4689 {
4690 // It should not happen because we are checking the capability in advance
4692 return false;
4693 }
4694 }
4695
4696 if ( storages.empty() )
4697 {
4698 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
4699 }
4700 else
4701 {
4702 emit messageLog( tr( "Reencrypt FAILED, could not find identity (id: %1)" ).arg( identid ), authManTag(), Qgis::MessageLevel::Critical );
4703 }
4704
4705 return false;
4706#else
4707 Q_UNUSED( identid )
4708 Q_UNUSED( prevpass )
4709 Q_UNUSED( prevciv )
4710 return false;
4711#endif
4712}
4713
4714#ifndef QT_NO_SSL
4715void QgsAuthManager::insertCaCertInCache( QgsAuthCertUtils::CaCertSource source, const QList<QSslCertificate> &certs )
4716{
4717#ifdef HAVE_AUTH
4719
4720 for ( const auto &cert : certs )
4721 {
4722 mCaCertsCache.insert( QgsAuthCertUtils::shaHexForCert( cert ), QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate>( source, cert ) );
4723 }
4724#else
4725 Q_UNUSED( source )
4726 Q_UNUSED( certs )
4727#endif
4728}
4729#endif
4730
4731QString QgsAuthManager::authPasswordHelperKeyName() const
4732{
4733#ifdef HAVE_AUTH
4735
4736 QString dbProfilePath;
4737
4738 // TODO: get the current profile name from the application
4739
4740 if ( isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
4741 {
4742 const QFileInfo info( mAuthDatabaseConnectionUri );
4743 dbProfilePath = info.dir().dirName();
4744 }
4745 else
4746 {
4747 dbProfilePath = QCryptographicHash::hash( ( mAuthDatabaseConnectionUri.toUtf8() ), QCryptographicHash::Md5 ).toHex();
4748 }
4749
4750 // if not running from the default profile, ensure that a different key is used
4751 return AUTH_PASSWORD_HELPER_KEY_NAME_BASE + ( dbProfilePath.compare( "default"_L1, Qt::CaseInsensitive ) == 0 ? QString() : dbProfilePath );
4752#else
4753 return QString();
4754#endif
4755}
4756
4758{
4759#ifdef HAVE_AUTH
4761 const auto storages = storageRegistry->readyStorages();
4762 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4763 {
4764 if ( qobject_cast<QgsAuthConfigurationStorageDb *>( storage ) )
4765 {
4766 return static_cast<QgsAuthConfigurationStorageDb *>( storage );
4767 }
4768 }
4769#endif
4770 return nullptr;
4771}
4772
4773QgsAuthConfigurationStorage *QgsAuthManager::firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability capability ) const
4774{
4775#ifdef HAVE_AUTH
4777 return storageRegistry->firstReadyStorageWithCapability( capability );
4778#else
4779 Q_UNUSED( capability )
4780 return nullptr;
4781#endif
4782}
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 delete 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.
QMap< QString, QVariant > settings() const
Returns the settings of the storage.
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:8379
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8378
QHash< QString, QgsAuthMethodConfig > QgsAuthMethodConfigsMap
QHash< QString, QgsAuthMethod * > QgsAuthMethodsMap
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71