QGIS API Documentation 3.41.0-Master (3440c17df1d)
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 <QDir>
18#include <QEventLoop>
19#include <QFile>
20#include <QFileInfo>
21#include <QMutexLocker>
22#include <QObject>
23#include <QSet>
24#include <QSqlDatabase>
25#include <QSqlError>
26#include <QSqlQuery>
27#include <QTextStream>
28#include <QTime>
29#include <QTimer>
30#include <QVariant>
31#include <QSqlDriver>
32#include <QDomElement>
33#include <QDomDocument>
34#include <QRegularExpression>
35#include <QCoreApplication>
36#include <QRandomGenerator>
37
38#include <QtCrypto>
39
40#ifndef QT_NO_SSL
41#include <QSslConfiguration>
42#endif
43
44// QGIS includes
45#include "qgsauthcertutils.h"
46#include "qgsauthcrypto.h"
47#include "qgsauthmethod.h"
50#include "qgscredentials.h"
51#include "qgslogger.h"
52#include "qgsmessagelog.h"
53#include "qgsauthmanager.h"
54#include "moc_qgsauthmanager.cpp"
57#include "qgsvariantutils.h"
58#include "qgssettings.h"
59#include "qgsruntimeprofiler.h"
60
61QgsAuthManager *QgsAuthManager::sInstance = nullptr;
62
63const QString QgsAuthManager::AUTH_CONFIG_TABLE = QStringLiteral( "auth_configs" );
64const QString QgsAuthManager::AUTH_SERVERS_TABLE = QStringLiteral( "auth_servers" );
65const QString QgsAuthManager::AUTH_MAN_TAG = QObject::tr( "Authentication Manager" );
66const QString QgsAuthManager::AUTH_CFG_REGEX = QStringLiteral( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );
67
68
69const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME_BASE( "QGIS-Master-Password" );
70const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_FOLDER_NAME( "QGIS" );
71
72
73
74#if defined(Q_OS_MAC)
76#elif defined(Q_OS_WIN)
77const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Password Manager" );
78#elif defined(Q_OS_LINUX)
79const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( QStringLiteral( "Wallet/KeyRing" ) );
80#else
81const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Password Manager" );
82#endif
83
85{
86 static QMutex sMutex;
87 QMutexLocker locker( &sMutex );
88 if ( !sInstance )
89 {
90 sInstance = new QgsAuthManager( );
91 }
92 return sInstance;
93}
94
95
97{
98 mMutex = std::make_unique<QRecursiveMutex>();
99 mMasterPasswordMutex = std::make_unique<QRecursiveMutex>();
100 connect( this, &QgsAuthManager::messageLog,
101 this, &QgsAuthManager::writeToConsole );
102}
103
105{
107
108 QSqlDatabase authdb;
109
110 if ( isDisabled() )
111 return authdb;
112
113 // while everything we use from QSqlDatabase here is thread safe, we need to ensure
114 // that the connection cleanup on thread finalization happens in a predictable order
115 QMutexLocker locker( mMutex.get() );
116
117 // Get the first enabled DB storage from the registry
119 {
120 return storage->authDatabaseConnection();
121 }
122
123 return authdb;
124}
125
127{
128 if ( ! isDisabled() )
129 {
131
132 // Returns the first enabled and ready "DB" storage
134 const QList<QgsAuthConfigurationStorage *> storages { storageRegistry->readyStorages() };
135 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
136 {
137 if ( auto dbStorage = qobject_cast<QgsAuthConfigurationStorageDb *>( storage ) )
138 {
139 if ( dbStorage->capabilities() & Qgis::AuthConfigurationStorageCapability::ReadConfiguration )
140 {
141 return dbStorage->quotedQualifiedIdentifier( dbStorage->methodConfigTableName() );
142 }
143 }
144 }
145 }
146
147 return QString();
148}
149
151{
152 // Loop through all registered SQL drivers and return false if
153 // the URI starts with one of them except the SQLite based drivers
154 const auto drivers { QSqlDatabase::drivers() };
155 for ( const QString &driver : std::as_const( drivers ) )
156 {
157 if ( driver != ( QStringLiteral( "QSQLITE" ) ) && driver != ( QStringLiteral( "QSPATIALITE" ) ) && uri.startsWith( driver ) )
158 {
159 return false;
160 }
161 }
162 return true;
163}
164
166{
167 return mAuthDatabaseConnectionUri;
168}
169
171{
172 QRegularExpression re( QStringLiteral( "password=(.*)" ) );
173 QString uri = mAuthDatabaseConnectionUri;
174 return uri.replace( re, QStringLiteral( "password=*****" ) );
175}
176
177
178bool QgsAuthManager::init( const QString &pluginPath, const QString &authDatabasePath )
179{
180 mAuthDatabaseConnectionUri = authDatabasePath.startsWith( QLatin1String( "QSQLITE://" ) ) ? authDatabasePath : QStringLiteral( "QSQLITE://" ) + authDatabasePath;
181 return initPrivate( pluginPath );
182}
183
185{
186 static QRecursiveMutex sInitializationMutex;
187 static bool sInitialized = false;
188
189 sInitializationMutex.lock();
190 if ( sInitialized )
191 {
192 sInitializationMutex.unlock();
193 return mLazyInitResult;
194 }
195
196 mLazyInitResult = const_cast< QgsAuthManager * >( this )->initPrivate( mPluginPath );
197 sInitialized = true;
198 sInitializationMutex.unlock();
199
200 return mLazyInitResult;
201}
202
203bool QgsAuthManager::initPrivate( const QString &pluginPath )
204{
205 if ( mAuthInit )
206 return true;
207
208 mAuthInit = true;
209 QgsScopedRuntimeProfile profile( tr( "Initializing authentication manager" ) );
210
211 QgsDebugMsgLevel( QStringLiteral( "Initializing QCA..." ), 2 );
212 mQcaInitializer = std::make_unique<QCA::Initializer>( QCA::Practical, 256 );
213
214 QgsDebugMsgLevel( QStringLiteral( "QCA initialized." ), 2 );
215 QCA::scanForPlugins();
216
217 QgsDebugMsgLevel( QStringLiteral( "QCA Plugin Diagnostics Context: %1" ).arg( QCA::pluginDiagnosticText() ), 2 );
218 QStringList capabilities;
219
220 capabilities = QCA::supportedFeatures();
221 QgsDebugMsgLevel( QStringLiteral( "QCA supports: %1" ).arg( capabilities.join( "," ) ), 2 );
222
223 // do run-time check for qca-ossl plugin
224 if ( !QCA::isSupported( "cert", QStringLiteral( "qca-ossl" ) ) )
225 {
226 mAuthDisabled = true;
227 mAuthDisabledMessage = tr( "QCA's OpenSSL plugin (qca-ossl) is missing" );
228 return isDisabled();
229 }
230
231 QgsDebugMsgLevel( QStringLiteral( "Prioritizing qca-ossl over all other QCA providers..." ), 2 );
232 const QCA::ProviderList provds = QCA::providers();
233 QStringList prlist;
234 for ( QCA::Provider *p : provds )
235 {
236 QString pn = p->name();
237 int pr = 0;
238 if ( pn != QLatin1String( "qca-ossl" ) )
239 {
240 pr = QCA::providerPriority( pn ) + 1;
241 }
242 QCA::setProviderPriority( pn, pr );
243 prlist << QStringLiteral( "%1:%2" ).arg( pn ).arg( QCA::providerPriority( pn ) );
244 }
245 QgsDebugMsgLevel( QStringLiteral( "QCA provider priorities: %1" ).arg( prlist.join( ", " ) ), 2 );
246
247 QgsDebugMsgLevel( QStringLiteral( "Populating auth method registry" ), 3 );
249
250 QStringList methods = authreg->authMethodList();
251
252 QgsDebugMsgLevel( QStringLiteral( "Authentication methods found: %1" ).arg( methods.join( ", " ) ), 2 );
253
254 if ( methods.isEmpty() )
255 {
256 mAuthDisabled = true;
257 mAuthDisabledMessage = tr( "No authentication method plugins found" );
258 return isDisabled();
259 }
260
262 {
263 mAuthDisabled = true;
264 mAuthDisabledMessage = tr( "No authentication method plugins could be loaded" );
265 return isDisabled();
266 }
267
268 QgsDebugMsgLevel( QStringLiteral( "Auth database URI: %1" ).arg( mAuthDatabaseConnectionUri ), 2 );
269
270 // Add the default configuration storage
271 const QString sqliteDbPath { sqliteDatabasePath() };
272 if ( ! sqliteDbPath.isEmpty() )
273 {
274 authConfigurationStorageRegistry()->addStorage( new QgsAuthConfigurationStorageSqlite( sqliteDbPath ) );
275 }
276 else if ( ! mAuthDatabaseConnectionUri.isEmpty() )
277 {
278 // For safety reasons we don't allow writing on potentially shared storages by default, plugins may override
279 // this behavior by registering their own storage subclass or by explicitly setting read-only to false.
280 QgsAuthConfigurationStorageDb *storage = new QgsAuthConfigurationStorageDb( mAuthDatabaseConnectionUri );
281 if ( !QgsAuthManager::isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
282 {
283 storage->setReadOnly( true );
284 }
286 }
287
288 // Loop through all registered storages and call initialize
289 const QList<QgsAuthConfigurationStorage *> storages { authConfigurationStorageRegistry()->storages() };
290 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
291 {
292 if ( ! storage->isEnabled() )
293 {
294 QgsDebugMsgLevel( QStringLiteral( "Storage %1 is disabled" ).arg( storage->name() ), 2 );
295 continue;
296 }
297 if ( !storage->initialize() )
298 {
299 const QString err = tr( "Failed to initialize storage %1: %2" ).arg( storage->name(), storage->lastError() );
300 QgsDebugError( err );
302 }
303 else
304 {
305 QgsDebugMsgLevel( QStringLiteral( "Storage %1 initialized" ).arg( storage->name() ), 2 );
306 }
307 connect( storage, &QgsAuthConfigurationStorage::methodConfigChanged, this, [this] { updateConfigAuthMethods(); } );
309 }
310
312
313#ifndef QT_NO_SSL
315#endif
316 // set the master password from first line of file defined by QGIS_AUTH_PASSWORD_FILE env variable
317 const char *passenv = "QGIS_AUTH_PASSWORD_FILE";
318 if ( getenv( passenv ) && masterPasswordHashInDatabase() )
319 {
320 QString passpath( getenv( passenv ) );
321 // clear the env variable, so it can not be accessed from plugins, etc.
322 // (note: stored QgsApplication::systemEnvVars() skips this env variable as well)
323#ifdef Q_OS_WIN
324 putenv( passenv );
325#else
326 unsetenv( passenv );
327#endif
328
329 QString masterpass;
330 QFile passfile( passpath );
331 if ( passfile.exists() && passfile.open( QIODevice::ReadOnly | QIODevice::Text ) )
332 {
333 QTextStream passin( &passfile );
334 while ( !passin.atEnd() )
335 {
336 masterpass = passin.readLine();
337 break;
338 }
339 passfile.close();
340 }
341 if ( !masterpass.isEmpty() )
342 {
343 if ( setMasterPassword( masterpass, true ) )
344 {
345 QgsDebugMsgLevel( QStringLiteral( "Authentication master password set from QGIS_AUTH_PASSWORD_FILE" ), 2 );
346 }
347 else
348 {
349 QgsDebugError( "QGIS_AUTH_PASSWORD_FILE set, but FAILED to set password using: " + passpath );
350 return false;
351 }
352 }
353 else
354 {
355 QgsDebugError( "QGIS_AUTH_PASSWORD_FILE set, but FAILED to read password from: " + passpath );
356 return false;
357 }
358 }
359
360#ifndef QT_NO_SSL
362#endif
363
364 return true;
365}
366
367void QgsAuthManager::setup( const QString &pluginPath, const QString &authDatabasePath )
368{
369 mPluginPath = pluginPath;
370 mAuthDatabaseConnectionUri = authDatabasePath;
371}
372
374{
376
377 if ( mAuthDisabled )
378 {
379 QgsDebugError( QStringLiteral( "Authentication system DISABLED: QCA's qca-ossl (OpenSSL) plugin is missing" ) );
380 }
381 return mAuthDisabled;
382}
383
385{
387
388 return tr( "Authentication system is DISABLED:\n%1" ).arg( mAuthDisabledMessage );
389}
390
391
392const QString QgsAuthManager::sqliteDatabasePath() const
393{
394 if ( !QgsAuthManager::isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
395 {
396 return QString();
397 }
398
399 // Remove the driver:// prefix if present
400 QString path = mAuthDatabaseConnectionUri;
401 if ( path.startsWith( QStringLiteral( "QSQLITE://" ), Qt::CaseSensitivity::CaseInsensitive ) )
402 {
403 path = path.mid( 10 );
404 }
405 else if ( path.startsWith( QStringLiteral( "QSPATIALITE://" ), Qt::CaseSensitivity::CaseInsensitive ) )
406 {
407 path = path.mid( 14 );
408 }
409
410 return QDir::cleanPath( path );
411}
412
414{
415 return sqliteDatabasePath();
416}
417
419{
421
422 QMutexLocker locker( mMasterPasswordMutex.get() );
423 if ( isDisabled() )
424 return false;
425
426 if ( mScheduledDbErase )
427 return false;
428
429 if ( mMasterPass.isEmpty() )
430 {
431 QgsDebugMsgLevel( QStringLiteral( "Master password is not yet set by user" ), 2 );
432 if ( !masterPasswordInput() )
433 {
434 QgsDebugMsgLevel( QStringLiteral( "Master password input canceled by user" ), 2 );
435 return false;
436 }
437 }
438 else
439 {
440 QgsDebugMsgLevel( QStringLiteral( "Master password is set" ), 2 );
441 if ( !verify )
442 return true;
443 }
444
445 if ( !verifyMasterPassword() )
446 return false;
447
448 QgsDebugMsgLevel( QStringLiteral( "Master password is set and verified" ), 2 );
449 return true;
450}
451
452bool QgsAuthManager::setMasterPassword( const QString &pass, bool verify )
453{
455
456 QMutexLocker locker( mMutex.get() );
457 if ( isDisabled() )
458 return false;
459
460 if ( mScheduledDbErase )
461 return false;
462
463 // since this is generally for automation, we don't care if passed-in is same as existing
464 QString prevpass = QString( mMasterPass );
465 mMasterPass = pass;
466 if ( verify && !verifyMasterPassword() )
467 {
468 mMasterPass = prevpass;
469 const char *err = QT_TR_NOOP( "Master password set: FAILED to verify, reset to previous" );
470 QgsDebugError( err );
472 return false;
473 }
474
475 QgsDebugMsgLevel( QStringLiteral( "Master password set: SUCCESS%1" ).arg( verify ? " and verified" : "" ), 2 );
476 return true;
477}
478
479bool QgsAuthManager::verifyMasterPassword( const QString &compare )
480{
482
483 if ( isDisabled() )
484 return false;
485
486 int rows = 0;
487 if ( !masterPasswordRowsInDb( &rows ) )
488 {
489 const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
490 QgsDebugError( err );
492
494 return false;
495 }
496
497 QgsDebugMsgLevel( QStringLiteral( "Master password: %1 rows in database" ).arg( rows ), 2 );
498
499 if ( rows > 1 )
500 {
501 const char *err = QT_TR_NOOP( "Master password: FAILED to find just one master password record in database" );
502 QgsDebugError( err );
504
506 return false;
507 }
508 else if ( rows == 1 )
509 {
510 if ( !masterPasswordCheckAgainstDb( compare ) )
511 {
512 if ( compare.isNull() ) // don't complain when comparing, since it could be an incomplete comparison string
513 {
514 const char *err = QT_TR_NOOP( "Master password: FAILED to verify against hash in database" );
515 QgsDebugError( err );
517
519
520 emit masterPasswordVerified( false );
521 }
522 ++mPassTries;
523 if ( mPassTries >= 5 )
524 {
525 mAuthDisabled = true;
526 const char *err = QT_TR_NOOP( "Master password: failed 5 times authentication system DISABLED" );
527 QgsDebugError( err );
529 }
530 return false;
531 }
532 else
533 {
534 QgsDebugMsgLevel( QStringLiteral( "Master password: verified against hash in database" ), 2 );
535 if ( compare.isNull() )
536 emit masterPasswordVerified( true );
537 }
538 }
539 else if ( compare.isNull() ) // compares should never be stored
540 {
541 if ( !masterPasswordStoreInDb() )
542 {
543 const char *err = QT_TR_NOOP( "Master password: hash FAILED to be stored in database" );
544 QgsDebugError( err );
546
548 return false;
549 }
550 else
551 {
552 QgsDebugMsgLevel( QStringLiteral( "Master password: hash stored in database" ), 2 );
553 }
554 // double-check storing
555 if ( !masterPasswordCheckAgainstDb() )
556 {
557 const char *err = QT_TR_NOOP( "Master password: FAILED to verify against hash in database" );
558 QgsDebugError( err );
560
562 emit masterPasswordVerified( false );
563 return false;
564 }
565 else
566 {
567 QgsDebugMsgLevel( QStringLiteral( "Master password: verified against hash in database" ), 2 );
568 emit masterPasswordVerified( true );
569 }
570 }
571
572 return true;
573}
574
576{
578
579 return !mMasterPass.isEmpty();
580}
581
582bool QgsAuthManager::masterPasswordSame( const QString &pass ) const
583{
585
586 return mMasterPass == pass;
587}
588
589bool QgsAuthManager::resetMasterPassword( const QString &newpass, const QString &oldpass,
590 bool keepbackup, QString *backuppath )
591{
593
594 if ( isDisabled() )
595 return false;
596
597 // verify caller knows the current master password
598 // this means that the user will have had to already set the master password as well
599 if ( !masterPasswordSame( oldpass ) )
600 return false;
601
602 QString dbbackup;
603 if ( !backupAuthenticationDatabase( &dbbackup ) )
604 return false;
605
606 QgsDebugMsgLevel( QStringLiteral( "Master password reset: backed up current database" ), 2 );
607
608 // store current password and civ
609 QString prevpass = QString( mMasterPass );
610 QString prevciv = QString( masterPasswordCiv() );
611
612 // on ANY FAILURE from this point, reinstate previous password and database
613 bool ok = true;
614
615 // clear password hash table (also clears mMasterPass)
616 if ( ok && !masterPasswordClearDb() )
617 {
618 ok = false;
619 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not clear current password from database" );
620 QgsDebugError( err );
622 }
623 if ( ok )
624 {
625 QgsDebugMsgLevel( QStringLiteral( "Master password reset: cleared current password from database" ), 2 );
626 }
627
628 // mMasterPass empty, set new password (don't verify, since not stored yet)
629 setMasterPassword( newpass, false );
630
631 // store new password hash
632 if ( ok && !masterPasswordStoreInDb() )
633 {
634 ok = false;
635 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not store new password in database" );
636 QgsDebugError( err );
638 }
639 if ( ok )
640 {
641 QgsDebugMsgLevel( QStringLiteral( "Master password reset: stored new password in database" ), 2 );
642 }
643
644 // verify it stored password properly
645 if ( ok && !verifyMasterPassword() )
646 {
647 ok = false;
648 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not verify new password in database" );
649 QgsDebugError( err );
651 }
652
653 // re-encrypt everything with new password
654 if ( ok && !reencryptAllAuthenticationConfigs( prevpass, prevciv ) )
655 {
656 ok = false;
657 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt configs in database" );
658 QgsDebugError( err );
660 }
661 if ( ok )
662 {
663 QgsDebugMsgLevel( QStringLiteral( "Master password reset: re-encrypted configs in database" ), 2 );
664 }
665
666 // verify it all worked
667 if ( ok && !verifyPasswordCanDecryptConfigs() )
668 {
669 ok = false;
670 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not verify password can decrypt re-encrypted configs" );
671 QgsDebugError( err );
673 }
674
675 if ( ok && !reencryptAllAuthenticationSettings( prevpass, prevciv ) )
676 {
677 ok = false;
678 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt settings in database" );
679 QgsDebugError( err );
681 }
682
683 if ( ok && !reencryptAllAuthenticationIdentities( prevpass, prevciv ) )
684 {
685 ok = false;
686 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt identities in database" );
687 QgsDebugError( err );
689 }
690
691 // something went wrong, reinstate previous password and database
692 if ( !ok )
693 {
694 // backup database of failed attempt, for inspection
695 QString errdbbackup( dbbackup );
696 errdbbackup.replace( QLatin1String( ".db" ), QLatin1String( "_ERROR.db" ) );
697 QFile::rename( sqliteDatabasePath(), errdbbackup );
698 QgsDebugError( QStringLiteral( "Master password reset FAILED: backed up failed db at %1" ).arg( errdbbackup ) );
699 // reinstate previous database and password
700 QFile::rename( dbbackup, sqliteDatabasePath() );
701 mMasterPass = prevpass;
702 QgsDebugError( QStringLiteral( "Master password reset FAILED: reinstated previous password and database" ) );
703
704 // assign error db backup
705 if ( backuppath )
706 *backuppath = errdbbackup;
707
708 return false;
709 }
710
711
712 if ( !keepbackup && !QFile::remove( dbbackup ) )
713 {
714 const char *err = QT_TR_NOOP( "Master password reset: could not remove old database backup" );
715 QgsDebugError( err );
717 // a non-blocking error, continue
718 }
719
720 if ( keepbackup )
721 {
722 QgsDebugMsgLevel( QStringLiteral( "Master password reset: backed up previous db at %1" ).arg( dbbackup ), 2 );
723 if ( backuppath )
724 *backuppath = dbbackup;
725 }
726
727 QgsDebugMsgLevel( QStringLiteral( "Master password reset: SUCCESS" ), 2 );
728 emit authDatabaseChanged();
729 return true;
730}
731
733{
735
736 mScheduledDbErase = scheduleErase;
737 // any call (start or stop) should reset these
738 mScheduledDbEraseRequestEmitted = false;
739 mScheduledDbEraseRequestCount = 0;
740
741 if ( scheduleErase )
742 {
743 if ( !mScheduledDbEraseTimer )
744 {
745 mScheduledDbEraseTimer = new QTimer( this );
746 connect( mScheduledDbEraseTimer, &QTimer::timeout, this, &QgsAuthManager::tryToStartDbErase );
747 mScheduledDbEraseTimer->start( mScheduledDbEraseRequestWait * 1000 );
748 }
749 else if ( !mScheduledDbEraseTimer->isActive() )
750 {
751 mScheduledDbEraseTimer->start();
752 }
753 }
754 else
755 {
756 if ( mScheduledDbEraseTimer && mScheduledDbEraseTimer->isActive() )
757 mScheduledDbEraseTimer->stop();
758 }
759}
760
762{
763 if ( isDisabled() )
764 return false;
765
766 qDeleteAll( mAuthMethods );
767 mAuthMethods.clear();
768 const QStringList methods = QgsAuthMethodRegistry::instance()->authMethodList();
769 for ( const auto &authMethodKey : methods )
770 {
771 mAuthMethods.insert( authMethodKey, QgsAuthMethodRegistry::instance()->createAuthMethod( authMethodKey ) );
772 }
773
774 return !mAuthMethods.isEmpty();
775}
776
778{
780
781 QStringList configids = configIds();
782 QString id;
783 int len = 7;
784
785 // Suppress warning: Potential leak of memory in qtimer.h [clang-analyzer-cplusplus.NewDeleteLeaks]
786#ifndef __clang_analyzer__
787 // sleep just a bit to make sure the current time has changed
788 QEventLoop loop;
789 QTimer::singleShot( 3, &loop, &QEventLoop::quit );
790 loop.exec();
791#endif
792
793 while ( true )
794 {
795 id.clear();
796 for ( int i = 0; i < len; i++ )
797 {
798 switch ( QRandomGenerator::system()->generate() % 2 )
799 {
800 case 0:
801 id += static_cast<char>( '0' + QRandomGenerator::system()->generate() % 10 );
802 break;
803 case 1:
804 id += static_cast<char>( 'a' + QRandomGenerator::system()->generate() % 26 );
805 break;
806 }
807 }
808 if ( !configids.contains( id ) )
809 {
810 break;
811 }
812 }
813 QgsDebugMsgLevel( QStringLiteral( "Generated unique ID: %1" ).arg( id ), 2 );
814 return id;
815}
816
817bool QgsAuthManager::configIdUnique( const QString &id ) const
818{
820
821 if ( isDisabled() )
822 return false;
823
824 if ( id.isEmpty() )
825 {
826 const char *err = QT_TR_NOOP( "Config ID is empty" );
827 QgsDebugError( err );
829 return false;
830 }
831 QStringList configids = configIds();
832 return !configids.contains( id );
833}
834
835bool QgsAuthManager::hasConfigId( const QString &txt )
836{
837 const thread_local QRegularExpression authCfgRegExp( AUTH_CFG_REGEX );
838 return txt.indexOf( authCfgRegExp ) != -1;
839}
840
842{
844
845 QMutexLocker locker( mMutex.get() );
846 QStringList providerAuthMethodsKeys;
847 if ( !dataprovider.isEmpty() )
848 {
849 providerAuthMethodsKeys = authMethodsKeys( dataprovider.toLower() );
850 }
851
852 QgsAuthMethodConfigsMap baseConfigs;
853
854 if ( isDisabled() )
855 return baseConfigs;
856
857 // Loop through all storages with capability ReadConfiguration and get the auth methods
859 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
860 {
861 QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
862 for ( const QgsAuthMethodConfig &config : std::as_const( configs ) )
863 {
864 if ( providerAuthMethodsKeys.isEmpty() || providerAuthMethodsKeys.contains( config.method() ) )
865 {
866 // Check if the config with that id is already in the list and warn if it is
867 if ( baseConfigs.contains( config.id() ) )
868 {
869 // This may not be an error, since the same config may be stored in multiple storages.
870 emit messageLog( tr( "A config with same id %1 was already added, skipping from %2" ).arg( config.id(), storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
871 }
872 else
873 {
874 baseConfigs.insert( config.id(), config );
875 }
876 }
877 }
878 }
879
880 if ( storages.empty() )
881 {
882 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
883 QgsDebugError( QStringLiteral( "No credentials storages found" ) );
884 }
885
886 return baseConfigs;
887
888}
889
891{
893
894 // Loop through all registered storages and get the auth methods
896 QStringList configIds;
897 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
898 {
899 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
900 for ( const QgsAuthMethodConfig &config : std::as_const( configs ) )
901 {
902 if ( ! configIds.contains( config.id() ) )
903 {
904 mConfigAuthMethods.insert( config.id(), config.method() );
905 QgsDebugMsgLevel( QStringLiteral( "Stored auth config/methods:\n%1 %2" ).arg( config.id(), config.method() ), 2 );
906 }
907 else
908 {
909 // This may not be an error, since the same config may be stored in multiple storages.
910 // A warning is issued when creating the list initially from availableAuthMethodConfigs()
911 QgsDebugMsgLevel( QStringLiteral( "A config with same id %1 was already added, skipping from %2" ).arg( config.id(), storage->name() ), 2 );
912 }
913 }
914 }
915}
916
918{
920
921 if ( isDisabled() )
922 return nullptr;
923
924 if ( !mConfigAuthMethods.contains( authcfg ) )
925 {
926 QgsDebugError( QStringLiteral( "No config auth method found in database for authcfg: %1" ).arg( authcfg ) );
927 return nullptr;
928 }
929
930 QString authMethodKey = mConfigAuthMethods.value( authcfg );
931
932 return authMethod( authMethodKey );
933}
934
935QString QgsAuthManager::configAuthMethodKey( const QString &authcfg ) const
936{
938
939 if ( isDisabled() )
940 return QString();
941
942 return mConfigAuthMethods.value( authcfg, QString() );
943}
944
945
946QStringList QgsAuthManager::authMethodsKeys( const QString &dataprovider )
947{
949
950 return authMethodsMap( dataprovider.toLower() ).keys();
951}
952
953QgsAuthMethod *QgsAuthManager::authMethod( const QString &authMethodKey )
954{
956
957 if ( !mAuthMethods.contains( authMethodKey ) )
958 {
959 QgsDebugError( QStringLiteral( "No auth method registered for auth method key: %1" ).arg( authMethodKey ) );
960 return nullptr;
961 }
962
963 return mAuthMethods.value( authMethodKey );
964}
965
966const QgsAuthMethodMetadata *QgsAuthManager::authMethodMetadata( const QString &authMethodKey )
967{
969
970 if ( !mAuthMethods.contains( authMethodKey ) )
971 {
972 QgsDebugError( QStringLiteral( "No auth method registered for auth method key: %1" ).arg( authMethodKey ) );
973 return nullptr;
974 }
975
976 return QgsAuthMethodRegistry::instance()->authMethodMetadata( authMethodKey );
977}
978
979
981{
983
984 if ( dataprovider.isEmpty() )
985 {
986 return mAuthMethods;
987 }
988
989 QgsAuthMethodsMap filteredmap;
990 QgsAuthMethodsMap::const_iterator i = mAuthMethods.constBegin();
991 while ( i != mAuthMethods.constEnd() )
992 {
993 if ( i.value()
994 && ( i.value()->supportedDataProviders().contains( QStringLiteral( "all" ) )
995 || i.value()->supportedDataProviders().contains( dataprovider ) ) )
996 {
997 filteredmap.insert( i.key(), i.value() );
998 }
999 ++i;
1000 }
1001 return filteredmap;
1002}
1003
1004#ifdef HAVE_GUI
1005QWidget *QgsAuthManager::authMethodEditWidget( const QString &authMethodKey, QWidget *parent )
1006{
1008
1009 QgsAuthMethod *method = authMethod( authMethodKey );
1010 if ( method )
1011 return method->editWidget( parent );
1012 else
1013 return nullptr;
1014}
1015#endif
1016
1018{
1020
1021 if ( isDisabled() )
1023
1024 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1025 if ( authmethod )
1026 {
1027 return authmethod->supportedExpansions();
1028 }
1030}
1031
1033{
1035
1036 QMutexLocker locker( mMutex.get() );
1037 if ( !setMasterPassword( true ) )
1038 return false;
1039
1040 // don't need to validate id, since it has not be defined yet
1041 if ( !config.isValid() )
1042 {
1043 const char *err = QT_TR_NOOP( "Store config: FAILED because config is invalid" );
1044 QgsDebugError( err );
1046 return false;
1047 }
1048
1049 QString uid = config.id();
1050 bool passedinID = !uid.isEmpty();
1051 if ( uid.isEmpty() )
1052 {
1053 uid = uniqueConfigId();
1054 }
1055 else if ( configIds().contains( uid ) )
1056 {
1057 if ( !overwrite )
1058 {
1059 const char *err = QT_TR_NOOP( "Store config: FAILED because pre-defined config ID %1 is not unique" );
1060 QgsDebugError( err );
1062 return false;
1063 }
1064 locker.unlock();
1065 if ( ! removeAuthenticationConfig( uid ) )
1066 {
1067 const char *err = QT_TR_NOOP( "Store config: FAILED because pre-defined config ID %1 could not be removed" );
1068 QgsDebugError( err );
1070 return false;
1071 }
1072 locker.relock();
1073 }
1074
1075 QString configstring = config.configString();
1076 if ( configstring.isEmpty() )
1077 {
1078 const char *err = QT_TR_NOOP( "Store config: FAILED because config string is empty" );
1079 QgsDebugError( err );
1081 return false;
1082 }
1083
1084 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateConfiguration ) )
1085 {
1086 if ( defaultStorage->isEncrypted() )
1087 {
1088 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
1089 }
1090
1091 // Make a copy to not alter the original config
1092 QgsAuthMethodConfig configCopy { config };
1093 configCopy.setId( uid );
1094 if ( !defaultStorage->storeMethodConfig( configCopy, configstring ) )
1095 {
1096 emit messageLog( tr( "Store config: FAILED to store config in default storage: %1" ).arg( defaultStorage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
1097 return false;
1098 }
1099 }
1100 else
1101 {
1102 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1103 return false;
1104 }
1105
1106 // passed-in config should now be like as if it was just loaded from db
1107 if ( !passedinID )
1108 config.setId( uid );
1109
1111
1112 QgsDebugMsgLevel( QStringLiteral( "Store config SUCCESS for authcfg: %1" ).arg( uid ), 2 );
1113 return true;
1114}
1115
1117{
1119
1120 QMutexLocker locker( mMutex.get() );
1121 if ( !setMasterPassword( true ) )
1122 return false;
1123
1124 // validate id
1125 if ( !config.isValid( true ) )
1126 {
1127 const char *err = QT_TR_NOOP( "Update config: FAILED because config is invalid" );
1128 QgsDebugError( err );
1130 return false;
1131 }
1132
1133 QString configstring = config.configString();
1134 if ( configstring.isEmpty() )
1135 {
1136 const char *err = QT_TR_NOOP( "Update config: FAILED because config is empty" );
1137 QgsDebugError( err );
1139 return false;
1140 }
1141
1142 // Loop through all storages with capability ReadConfiguration and update the first one that has the config
1144
1145 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1146 {
1147 if ( storage->methodConfigExists( config.id() ) )
1148 {
1150 {
1151 emit messageLog( tr( "Update config: FAILED because storage %1 does not support updating" ).arg( storage->name( ) ), authManTag(), Qgis::MessageLevel::Warning );
1152 return false;
1153 }
1154 if ( storage->isEncrypted() )
1155 {
1156 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
1157 }
1158 if ( !storage->storeMethodConfig( config, configstring ) )
1159 {
1160 emit messageLog( tr( "Store config: FAILED to store config in the storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1161 return false;
1162 }
1163 break;
1164 }
1165 }
1166
1167 if ( storages.empty() )
1168 {
1169 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1170 return false;
1171 }
1172
1173 // should come before updating auth methods, in case user switched auth methods in config
1174 clearCachedConfig( config.id() );
1175
1177
1178 QgsDebugMsgLevel( QStringLiteral( "Update config SUCCESS for authcfg: %1" ).arg( config.id() ), 2 );
1179
1180 return true;
1181}
1182
1183bool QgsAuthManager::loadAuthenticationConfig( const QString &authcfg, QgsAuthMethodConfig &config, bool full )
1184{
1186
1187 if ( isDisabled() )
1188 return false;
1189
1190 if ( full && !setMasterPassword( true ) )
1191 return false;
1192
1193 QMutexLocker locker( mMutex.get() );
1194
1195 // Loop through all storages with capability ReadConfiguration and get the config from the first one that has the config
1197
1198 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1199 {
1200 if ( storage->methodConfigExists( authcfg ) )
1201 {
1202 QString payload;
1203 config = storage->loadMethodConfig( authcfg, payload, full );
1204
1205 if ( ! config.isValid( true ) || ( full && payload.isEmpty() ) )
1206 {
1207 emit messageLog( tr( "Load config: FAILED to load config %1 from default storage: %2" ).arg( authcfg, storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1208 return false;
1209 }
1210
1211 if ( full )
1212 {
1213 if ( storage->isEncrypted() )
1214 {
1215 payload = QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), payload );
1216 }
1217 config.loadConfigString( payload );
1218 }
1219
1220 QString authMethodKey = configAuthMethodKey( authcfg );
1221 QgsAuthMethod *authmethod = authMethod( authMethodKey );
1222 if ( authmethod )
1223 {
1224 authmethod->updateMethodConfig( config );
1225 }
1226 else
1227 {
1228 QgsDebugError( QStringLiteral( "Update of authcfg %1 FAILED for auth method %2" ).arg( authcfg, authMethodKey ) );
1229 }
1230
1231 QgsDebugMsgLevel( QStringLiteral( "Load %1 config SUCCESS for authcfg: %2" ).arg( full ? "full" : "base", authcfg ), 2 );
1232 return true;
1233 }
1234 }
1235
1236 if ( storages.empty() )
1237 {
1238 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1239 }
1240 else
1241 {
1242 emit messageLog( tr( "Load config: FAILED to load config %1 from any storage" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
1243 }
1244
1245 return false;
1246}
1247
1249{
1251
1252 QMutexLocker locker( mMutex.get() );
1253 if ( isDisabled() )
1254 return false;
1255
1256 if ( authcfg.isEmpty() )
1257 return false;
1258
1259 // Loop through all storages with capability DeleteConfiguration and delete the first one that has the config
1261
1262 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1263 {
1264 if ( storage->methodConfigExists( authcfg ) )
1265 {
1266 if ( !storage->removeMethodConfig( authcfg ) )
1267 {
1268 emit messageLog( tr( "Remove config: FAILED to remove config from the storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1269 return false;
1270 }
1271 else
1272 {
1273 clearCachedConfig( authcfg );
1275 QgsDebugMsgLevel( QStringLiteral( "REMOVED config for authcfg: %1" ).arg( authcfg ), 2 );
1276 return true;
1277 }
1278 break;
1279 }
1280 }
1281
1282 if ( storages.empty() )
1283 {
1284 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1285 }
1286 else
1287 {
1288 emit messageLog( tr( "Remove config: FAILED to remove config %1 from any storage" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
1289 }
1290
1291 return false;
1292
1293}
1294
1295bool QgsAuthManager::exportAuthenticationConfigsToXml( const QString &filename, const QStringList &authcfgs, const QString &password )
1296{
1298
1299 if ( filename.isEmpty() )
1300 return false;
1301
1302 QDomDocument document( QStringLiteral( "qgis_authentication" ) );
1303 QDomElement root = document.createElement( QStringLiteral( "qgis_authentication" ) );
1304 document.appendChild( root );
1305
1306 QString civ;
1307 if ( !password.isEmpty() )
1308 {
1309 QString salt;
1310 QString hash;
1311 QgsAuthCrypto::passwordKeyHash( password, &salt, &hash, &civ );
1312 root.setAttribute( QStringLiteral( "salt" ), salt );
1313 root.setAttribute( QStringLiteral( "hash" ), hash );
1314 root.setAttribute( QStringLiteral( "civ" ), civ );
1315 }
1316
1317 QDomElement configurations = document.createElement( QStringLiteral( "configurations" ) );
1318 for ( const QString &authcfg : authcfgs )
1319 {
1320 QgsAuthMethodConfig authMethodConfig;
1321
1322 bool ok = loadAuthenticationConfig( authcfg, authMethodConfig, true );
1323 if ( ok )
1324 {
1325 authMethodConfig.writeXml( configurations, document );
1326 }
1327 }
1328 if ( !password.isEmpty() )
1329 {
1330 QString configurationsString;
1331 QTextStream ts( &configurationsString );
1332#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1333 ts.setCodec( "UTF-8" );
1334#endif
1335 configurations.save( ts, 2 );
1336 root.appendChild( document.createTextNode( QgsAuthCrypto::encrypt( password, civ, configurationsString ) ) );
1337 }
1338 else
1339 {
1340 root.appendChild( configurations );
1341 }
1342
1343 QFile file( filename );
1344 if ( !file.open( QFile::WriteOnly | QIODevice::Truncate ) )
1345 return false;
1346
1347 QTextStream ts( &file );
1348#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1349 ts.setCodec( "UTF-8" );
1350#endif
1351 document.save( ts, 2 );
1352 file.close();
1353 return true;
1354}
1355
1356bool QgsAuthManager::importAuthenticationConfigsFromXml( const QString &filename, const QString &password, bool overwrite )
1357{
1359
1360 QFile file( filename );
1361 if ( !file.open( QFile::ReadOnly ) )
1362 {
1363 return false;
1364 }
1365
1366 QDomDocument document( QStringLiteral( "qgis_authentication" ) );
1367 if ( !document.setContent( &file ) )
1368 {
1369 file.close();
1370 return false;
1371 }
1372 file.close();
1373
1374 QDomElement root = document.documentElement();
1375 if ( root.tagName() != QLatin1String( "qgis_authentication" ) )
1376 {
1377 return false;
1378 }
1379
1380 QDomElement configurations;
1381 if ( root.hasAttribute( QStringLiteral( "salt" ) ) )
1382 {
1383 QString salt = root.attribute( QStringLiteral( "salt" ) );
1384 QString hash = root.attribute( QStringLiteral( "hash" ) );
1385 QString civ = root.attribute( QStringLiteral( "civ" ) );
1386 if ( !QgsAuthCrypto::verifyPasswordKeyHash( password, salt, hash ) )
1387 return false;
1388
1389 document.setContent( QgsAuthCrypto::decrypt( password, civ, root.text() ) );
1390 configurations = document.firstChild().toElement();
1391 }
1392 else
1393 {
1394 configurations = root.firstChildElement( QStringLiteral( "configurations" ) );
1395 }
1396
1397 QDomElement configuration = configurations.firstChildElement();
1398 while ( !configuration.isNull() )
1399 {
1400 QgsAuthMethodConfig authMethodConfig;
1401 authMethodConfig.readXml( configuration );
1402 storeAuthenticationConfig( authMethodConfig, overwrite );
1403
1404 configuration = configuration.nextSiblingElement();
1405 }
1406 return true;
1407}
1408
1410{
1412
1413 QMutexLocker locker( mMutex.get() );
1414 if ( isDisabled() )
1415 return false;
1416
1417 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::DeleteConfiguration ) )
1418 {
1419 if ( defaultStorage->clearMethodConfigs() )
1420 {
1423 QgsDebugMsgLevel( QStringLiteral( "REMOVED all configs from the default storage" ), 2 );
1424 return true;
1425 }
1426 else
1427 {
1428 QgsDebugMsgLevel( QStringLiteral( "FAILED to remove all configs from the default storage" ), 2 );
1429 return false;
1430 }
1431 }
1432 else
1433 {
1434 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1435 return false;
1436 }
1437}
1438
1439
1441{
1443
1444 QMutexLocker locker( mMutex.get() );
1445
1446 if ( sqliteDatabasePath().isEmpty() )
1447 {
1448 const char *err = QT_TR_NOOP( "The authentication database is not filesystem-based" );
1449 QgsDebugError( err );
1451 return false;
1452 }
1453
1454 if ( !QFile::exists( sqliteDatabasePath() ) )
1455 {
1456 const char *err = QT_TR_NOOP( "No authentication database found" );
1457 QgsDebugError( err );
1459 return false;
1460 }
1461
1462 // close any connection to current db
1464 QSqlDatabase authConn = authDatabaseConnection();
1466 if ( authConn.isValid() && authConn.isOpen() )
1467 authConn.close();
1468
1469 // duplicate current db file to 'qgis-auth_YYYY-MM-DD-HHMMSS.db' backup
1470 QString datestamp( QDateTime::currentDateTime().toString( QStringLiteral( "yyyy-MM-dd-hhmmss" ) ) );
1471 QString dbbackup( sqliteDatabasePath() );
1472 dbbackup.replace( QLatin1String( ".db" ), QStringLiteral( "_%1.db" ).arg( datestamp ) );
1473
1474 if ( !QFile::copy( sqliteDatabasePath(), dbbackup ) )
1475 {
1476 const char *err = QT_TR_NOOP( "Could not back up authentication database" );
1477 QgsDebugError( err );
1479 return false;
1480 }
1481
1482 if ( backuppath )
1483 *backuppath = dbbackup;
1484
1485 QgsDebugMsgLevel( QStringLiteral( "Backed up auth database at %1" ).arg( dbbackup ), 2 );
1486 return true;
1487}
1488
1489bool QgsAuthManager::eraseAuthenticationDatabase( bool backup, QString *backuppath )
1490{
1492
1493 QMutexLocker locker( mMutex.get() );
1494 if ( isDisabled() )
1495 return false;
1496
1497 QString dbbackup;
1498 if ( backup && !backupAuthenticationDatabase( &dbbackup ) )
1499 {
1500 emit messageLog( tr( "Failed to backup authentication database" ), authManTag(), Qgis::MessageLevel::Warning );
1501 return false;
1502 }
1503
1504 if ( backuppath && !dbbackup.isEmpty() )
1505 *backuppath = dbbackup;
1506
1507 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ClearStorage ) )
1508 {
1509 if ( defaultStorage->erase() )
1510 {
1511 mMasterPass = QString();
1514 QgsDebugMsgLevel( QStringLiteral( "ERASED all configs" ), 2 );
1515 return true;
1516 }
1517 else
1518 {
1519 QgsDebugMsgLevel( QStringLiteral( "FAILED to erase all configs" ), 2 );
1520 return false;
1521 }
1522 }
1523 else
1524 {
1525 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1526 return false;
1527 }
1528
1529#ifndef QT_NO_SSL
1530 initSslCaches();
1531#endif
1532
1533 emit authDatabaseChanged();
1534
1535 return true;
1536}
1537
1538bool QgsAuthManager::updateNetworkRequest( QNetworkRequest &request, const QString &authcfg,
1539 const QString &dataprovider )
1540{
1542
1543 if ( isDisabled() )
1544 return false;
1545
1546 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1547 if ( authmethod )
1548 {
1549 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkRequest ) )
1550 {
1551 QgsDebugError( QStringLiteral( "Network request updating not supported by authcfg: %1" ).arg( authcfg ) );
1552 return true;
1553 }
1554
1555 if ( !authmethod->updateNetworkRequest( request, authcfg, dataprovider.toLower() ) )
1556 {
1557 authmethod->clearCachedConfig( authcfg );
1558 return false;
1559 }
1560 return true;
1561 }
1562 return false;
1563}
1564
1565bool QgsAuthManager::updateNetworkReply( QNetworkReply *reply, const QString &authcfg,
1566 const QString &dataprovider )
1567{
1569
1570 if ( isDisabled() )
1571 return false;
1572
1573 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1574 if ( authmethod )
1575 {
1576 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkReply ) )
1577 {
1578 QgsDebugMsgLevel( QStringLiteral( "Network reply updating not supported by authcfg: %1" ).arg( authcfg ), 3 );
1579 return true;
1580 }
1581
1582 if ( !authmethod->updateNetworkReply( reply, authcfg, dataprovider.toLower() ) )
1583 {
1584 authmethod->clearCachedConfig( authcfg );
1585 return false;
1586 }
1587 return true;
1588 }
1589
1590 return false;
1591}
1592
1593bool QgsAuthManager::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
1594 const QString &dataprovider )
1595{
1597
1598 if ( isDisabled() )
1599 return false;
1600
1601 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1602 if ( authmethod )
1603 {
1604 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::DataSourceUri ) )
1605 {
1606 QgsDebugError( QStringLiteral( "Data source URI updating not supported by authcfg: %1" ).arg( authcfg ) );
1607 return true;
1608 }
1609
1610 if ( !authmethod->updateDataSourceUriItems( connectionItems, authcfg, dataprovider.toLower() ) )
1611 {
1612 authmethod->clearCachedConfig( authcfg );
1613 return false;
1614 }
1615 return true;
1616 }
1617
1618 return false;
1619}
1620
1621bool QgsAuthManager::updateNetworkProxy( QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider )
1622{
1624
1625 if ( isDisabled() )
1626 return false;
1627
1628 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1629 if ( authmethod )
1630 {
1631 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkProxy ) )
1632 {
1633 QgsDebugError( QStringLiteral( "Proxy updating not supported by authcfg: %1" ).arg( authcfg ) );
1634 return true;
1635 }
1636
1637 if ( !authmethod->updateNetworkProxy( proxy, authcfg, dataprovider.toLower() ) )
1638 {
1639 authmethod->clearCachedConfig( authcfg );
1640 return false;
1641 }
1642 QgsDebugMsgLevel( QStringLiteral( "Proxy updated successfully from authcfg: %1" ).arg( authcfg ), 2 );
1643 return true;
1644 }
1645
1646 return false;
1647}
1648
1649bool QgsAuthManager::storeAuthSetting( const QString &key, const QVariant &value, bool encrypt )
1650{
1652
1653 QMutexLocker locker( mMutex.get() );
1654 if ( key.isEmpty() )
1655 return false;
1656
1657 QString storeval( value.toString() );
1658 if ( encrypt )
1659 {
1660 if ( !setMasterPassword( true ) )
1661 {
1662 return false;
1663 }
1664 else
1665 {
1666 storeval = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), value.toString() );
1667 }
1668 }
1669
1670 if ( existsAuthSetting( key ) && ! removeAuthSetting( key ) )
1671 {
1672 emit messageLog( tr( "Store setting: FAILED to remove pre-existing setting %1" ).arg( key ), authManTag(), Qgis::MessageLevel::Warning );
1673 return false;
1674 }
1675
1676 // Set the setting in the first storage that has the capability to store it
1677
1678 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateSetting ) )
1679 {
1680 if ( !defaultStorage->storeAuthSetting( key, storeval ) )
1681 {
1682 emit messageLog( tr( "Store setting: FAILED to store setting in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
1683 return false;
1684 }
1685 return true;
1686 }
1687 else
1688 {
1689 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1690 return false;
1691 }
1692}
1693
1694QVariant QgsAuthManager::authSetting( const QString &key, const QVariant &defaultValue, bool decrypt )
1695{
1697
1698 QMutexLocker locker( mMutex.get() );
1699 if ( key.isEmpty() )
1700 return QVariant();
1701
1702 if ( decrypt && !setMasterPassword( true ) )
1703 return QVariant();
1704
1705 QVariant value = defaultValue;
1706
1707 // Loop through all storages with capability ReadSetting and get the setting from the first one that has the setting
1709
1710 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1711 {
1712 QString storeval = storage->loadAuthSetting( key );
1713 if ( !storeval.isEmpty() )
1714 {
1715 if ( decrypt )
1716 {
1717 storeval = QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), storeval );
1718 }
1719 value = storeval;
1720 break;
1721 }
1722 }
1723
1724 if ( storages.empty() )
1725 {
1726 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1727 }
1728
1729 return value;
1730}
1731
1732bool QgsAuthManager::existsAuthSetting( const QString &key )
1733{
1735
1736 QMutexLocker locker( mMutex.get() );
1737 if ( key.isEmpty() )
1738 return false;
1739
1740 // Loop through all storages with capability ReadSetting and get the setting from the first one that has the setting
1742
1743 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1744 {
1745
1746 if ( storage->authSettingExists( key ) )
1747 { return true; }
1748
1749 }
1750
1751 if ( storages.empty() )
1752 {
1753 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1754 }
1755
1756 return false;
1757}
1758
1759bool QgsAuthManager::removeAuthSetting( const QString &key )
1760{
1762
1763 QMutexLocker locker( mMutex.get() );
1764 if ( key.isEmpty() )
1765 return false;
1766
1767 // Loop through all storages with capability ReadSetting and delete from the first one that has the setting, fail if it has no capability
1769
1770 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1771 {
1772 if ( storage->authSettingExists( key ) )
1773 {
1775 {
1776 if ( !storage->removeAuthSetting( key ) )
1777 {
1778 emit messageLog( tr( "Remove setting: FAILED to remove setting from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
1779 return false;
1780 }
1781 return true;
1782 }
1783 else
1784 {
1785 emit messageLog( tr( "Remove setting: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
1786 return false;
1787 }
1788 }
1789 }
1790
1791 if ( storages.empty() )
1792 {
1793 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1794 }
1795 return false;
1796}
1797
1798#ifndef QT_NO_SSL
1799
1801
1803{
1804 QgsScopedRuntimeProfile profile( "Initialize SSL cache" );
1805
1806 QMutexLocker locker( mMutex.get() );
1807 bool res = true;
1808 res = res && rebuildCaCertsCache();
1809 res = res && rebuildCertTrustCache();
1810 res = res && rebuildTrustedCaCertsCache();
1811 res = res && rebuildIgnoredSslErrorCache();
1812 mCustomConfigByHostCache.clear();
1813 mHasCheckedIfCustomConfigByHostExists = false;
1814
1815 if ( !res )
1816 QgsDebugError( QStringLiteral( "Init of SSL caches FAILED" ) );
1817 return res;
1818}
1819
1820bool QgsAuthManager::storeCertIdentity( const QSslCertificate &cert, const QSslKey &key )
1821{
1823
1824 QMutexLocker locker( mMutex.get() );
1825 if ( cert.isNull() )
1826 {
1827 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
1828 return false;
1829 }
1830 if ( key.isNull() )
1831 {
1832 QgsDebugError( QStringLiteral( "Passed private key is null" ) );
1833 return false;
1834 }
1835
1836 if ( !setMasterPassword( true ) )
1837 return false;
1838
1839 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
1840
1841
1842 if ( existsCertIdentity( id ) && ! removeCertIdentity( id ) )
1843 {
1844 QgsDebugError( QStringLiteral( "Store certificate identity: FAILED to remove pre-existing certificate identity %1" ).arg( id ) );
1845 return false;
1846 }
1847
1848 QString keypem( QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), key.toPem() ) );
1849
1851 {
1852 if ( !defaultStorage->storeCertIdentity( cert, keypem ) )
1853 {
1854 emit messageLog( tr( "Store certificate identity: FAILED to store certificate identity in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
1855 return false;
1856 }
1857 return true;
1858 }
1859 else
1860 {
1861 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1862 return false;
1863 }
1864}
1865
1866const QSslCertificate QgsAuthManager::certIdentity( const QString &id )
1867{
1869
1870 QMutexLocker locker( mMutex.get() );
1871
1872 QSslCertificate cert;
1873
1874 if ( id.isEmpty() )
1875 return cert;
1876
1877 // Loop through all storages with capability ReadCertificateIdentity and get the certificate from the first one that has the certificate
1879
1880 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1881 {
1882 cert = storage->loadCertIdentity( id );
1883 if ( !cert.isNull() )
1884 {
1885 return cert;
1886 }
1887 }
1888
1889 if ( storages.empty() )
1890 {
1891 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1892 }
1893
1894 return cert;
1895}
1896
1897const QPair<QSslCertificate, QSslKey> QgsAuthManager::certIdentityBundle( const QString &id )
1898{
1900
1901 QMutexLocker locker( mMutex.get() );
1902 QPair<QSslCertificate, QSslKey> bundle;
1903 if ( id.isEmpty() )
1904 return bundle;
1905
1906 if ( !setMasterPassword( true ) )
1907 return bundle;
1908
1909 // Loop through all storages with capability ReadCertificateIdentity and get the certificate from the first one that has the certificate
1911
1912 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1913 {
1914 if ( storage->certIdentityExists( id ) )
1915 {
1916 QPair<QSslCertificate, QString> encryptedBundle { storage->loadCertIdentityBundle( id ) };
1917 if ( encryptedBundle.first.isNull() )
1918 {
1919 QgsDebugError( QStringLiteral( "Certificate identity bundle is null for id: %1" ).arg( id ) );
1920 return bundle;
1921 }
1922 QSslKey key( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), encryptedBundle.second ).toLatin1(),
1923 QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey );
1924 if ( key.isNull() )
1925 {
1926 QgsDebugError( QStringLiteral( "Certificate identity bundle: FAILED to create private key" ) );
1927 return bundle;
1928 }
1929 bundle = qMakePair( encryptedBundle.first, key );
1930 break;
1931 }
1932 }
1933
1934 if ( storages.empty() )
1935 {
1936 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1937 return bundle;
1938 }
1939
1940 return bundle;
1941}
1942
1943const QStringList QgsAuthManager::certIdentityBundleToPem( const QString &id )
1944{
1946
1947 QMutexLocker locker( mMutex.get() );
1948 QPair<QSslCertificate, QSslKey> bundle( certIdentityBundle( id ) );
1949 if ( QgsAuthCertUtils::certIsViable( bundle.first ) && !bundle.second.isNull() )
1950 {
1951 return QStringList() << QString( bundle.first.toPem() ) << QString( bundle.second.toPem() );
1952 }
1953 return QStringList();
1954}
1955
1956const QList<QSslCertificate> QgsAuthManager::certIdentities()
1957{
1959
1960 QMutexLocker locker( mMutex.get() );
1961 QList<QSslCertificate> certs;
1962
1963 // Loop through all storages with capability ReadCertificateIdentity and collect the certificates from all storages
1965
1966 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1967 {
1968 const QList<QSslCertificate> storageCerts = storage->certIdentities();
1969 // Add if not already in the list, warn otherwise
1970 for ( const QSslCertificate &cert : std::as_const( storageCerts ) )
1971 {
1972 if ( !certs.contains( cert ) )
1973 {
1974 certs.append( cert );
1975 }
1976 else
1977 {
1978 emit messageLog( tr( "Certificate already in the list: %1" ).arg( cert.issuerDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
1979 }
1980 }
1981 }
1982
1983 if ( storages.empty() )
1984 {
1985 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1986 }
1987
1988 return certs;
1989}
1990
1992{
1994
1995 QMutexLocker locker( mMutex.get() );
1996
1997 if ( isDisabled() )
1998 return {};
1999
2000 // Loop through all storages with capability ReadCertificateIdentity and collect the certificate ids from all storages
2002
2003 QStringList ids;
2004
2005 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2006 {
2007 const QStringList storageIds = storage->certIdentityIds();
2008 // Add if not already in the list, warn otherwise
2009 for ( const QString &id : std::as_const( storageIds ) )
2010 {
2011 if ( !ids.contains( id ) )
2012 {
2013 ids.append( id );
2014 }
2015 else
2016 {
2017 emit messageLog( tr( "Certificate identity id already in the list: %1" ).arg( id ), authManTag(), Qgis::MessageLevel::Warning );
2018 }
2019 }
2020 }
2021
2022 return ids;
2023}
2024
2025bool QgsAuthManager::existsCertIdentity( const QString &id )
2026{
2028
2029 QMutexLocker locker( mMutex.get() );
2030 if ( id.isEmpty() )
2031 return false;
2032
2033 // Loop through all storages with capability ReadCertificateIdentity and check if the certificate exists in any storage
2035
2036 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2037 {
2038 if ( storage->certIdentityExists( id ) )
2039 {
2040 return true;
2041 }
2042 }
2043
2044 if ( storages.empty() )
2045 {
2046 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2047 }
2048
2049 return false;
2050}
2051
2052bool QgsAuthManager::removeCertIdentity( const QString &id )
2053{
2055
2056 QMutexLocker locker( mMutex.get() );
2057 if ( id.isEmpty() )
2058 {
2059 QgsDebugError( QStringLiteral( "Passed bundle ID is empty" ) );
2060 return false;
2061 }
2062
2063 // Loop through all storages with capability ReadCertificateIdentity and delete from the first one that has the bundle, fail if it has no capability
2065
2066 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2067 {
2068 if ( storage->certIdentityExists( id ) )
2069 {
2070 if ( !storage->removeCertIdentity( id ) )
2071 {
2072 emit messageLog( tr( "Remove certificate identity: FAILED to remove certificate identity from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2073 return false;
2074 }
2075 return true;
2076 }
2077 }
2078
2079 if ( storages.empty() )
2080 {
2081 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2082 }
2083
2084 return false;
2085
2086}
2087
2089{
2091
2092 QMutexLocker locker( mMutex.get() );
2093 if ( config.isNull() )
2094 {
2095 QgsDebugError( QStringLiteral( "Passed config is null" ) );
2096 return false;
2097 }
2098
2099 const QSslCertificate cert( config.sslCertificate() );
2100 const QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2101
2102 if ( existsSslCertCustomConfig( id, config.sslHostPort() ) && !removeSslCertCustomConfig( id, config.sslHostPort() ) )
2103 {
2104 QgsDebugError( QStringLiteral( "Store SSL certificate custom config: FAILED to remove pre-existing config %1" ).arg( id ) );
2105 return false;
2106 }
2107
2109 {
2110 if ( !defaultStorage->storeSslCertCustomConfig( config ) )
2111 {
2112 emit messageLog( tr( "Store SSL certificate custom config: FAILED to store config in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
2113 return false;
2114 }
2115 }
2116 else
2117 {
2118 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2119 return false;
2120 }
2121
2123 mCustomConfigByHostCache.clear();
2124
2125 return true;
2126}
2127
2128const QgsAuthConfigSslServer QgsAuthManager::sslCertCustomConfig( const QString &id, const QString &hostport )
2129{
2131
2132 QMutexLocker locker( mMutex.get() );
2134
2135 if ( id.isEmpty() || hostport.isEmpty() )
2136 {
2137 QgsDebugError( QStringLiteral( "Passed config ID or host:port is empty" ) );
2138 return config;
2139 }
2140
2141 // Loop through all storages with capability ReadSslCertificateCustomConfig and get the config from the first one that has the config
2143
2144 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2145 {
2146 if ( storage->sslCertCustomConfigExists( id, hostport ) )
2147 {
2148 config = storage->loadSslCertCustomConfig( id, hostport );
2149 if ( !config.isNull() )
2150 {
2151 return config;
2152 }
2153 else
2154 {
2155 emit messageLog( tr( "Could not load SSL custom config %1 %2 from the storage." ).arg( id, hostport ), authManTag(), Qgis::MessageLevel::Critical );
2156 return config;
2157 }
2158 }
2159 }
2160
2161 if ( storages.empty() )
2162 {
2163 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2164 }
2165
2166 return config;
2167
2168}
2169
2171{
2173
2175 if ( hostport.isEmpty() )
2176 {
2177 return config;
2178 }
2179
2180 QMutexLocker locker( mMutex.get() );
2181
2182 if ( mCustomConfigByHostCache.contains( hostport ) )
2183 return mCustomConfigByHostCache.value( hostport );
2184
2185 // Loop through all storages with capability ReadSslCertificateCustomConfig and get the config from the first one that has the config
2187
2188 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2189 {
2190 config = storage->loadSslCertCustomConfigByHost( hostport );
2191 if ( !config.isNull() )
2192 {
2193 mCustomConfigByHostCache.insert( hostport, config );
2194 }
2195
2196 }
2197
2198 if ( storages.empty() )
2199 {
2200 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2201 }
2202
2203 return config;
2204}
2205
2206const QList<QgsAuthConfigSslServer> QgsAuthManager::sslCertCustomConfigs()
2207{
2209
2210 QMutexLocker locker( mMutex.get() );
2211 QList<QgsAuthConfigSslServer> configs;
2212
2213 // Loop through all storages with capability ReadSslCertificateCustomConfig
2215
2216 QStringList ids;
2217
2218 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2219 {
2220 const QList<QgsAuthConfigSslServer> storageConfigs = storage->sslCertCustomConfigs();
2221 // Check if id + hostPort is not already in the list, warn otherwise
2222 for ( const auto &config : std::as_const( storageConfigs ) )
2223 {
2224 const QString id( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ) );
2225 const QString hostPort = config.sslHostPort();
2226 const QString shaHostPort( QStringLiteral( "%1:%2" ).arg( id, hostPort ) );
2227 if ( ! ids.contains( shaHostPort ) )
2228 {
2229 ids.append( shaHostPort );
2230 configs.append( config );
2231 }
2232 else
2233 {
2234 emit messageLog( tr( "SSL custom config already in the list: %1" ).arg( hostPort ), authManTag(), Qgis::MessageLevel::Warning );
2235 }
2236 }
2237 }
2238
2239 if ( storages.empty() )
2240 {
2241 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2242 }
2243
2244 return configs;
2245}
2246
2247bool QgsAuthManager::existsSslCertCustomConfig( const QString &id, const QString &hostPort )
2248{
2250
2251 QMutexLocker locker( mMutex.get() );
2252 if ( id.isEmpty() || hostPort.isEmpty() )
2253 {
2254 QgsDebugError( QStringLiteral( "Passed config ID or host:port is empty" ) );
2255 return false;
2256 }
2257
2258 // Loop through all storages with capability ReadSslCertificateCustomConfig
2260
2261 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2262 {
2263 if ( storage->sslCertCustomConfigExists( id, hostPort ) )
2264 {
2265 return true;
2266 }
2267 }
2268
2269 if ( storages.empty() )
2270 {
2271 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2272 }
2273
2274 return false;
2275}
2276
2277bool QgsAuthManager::removeSslCertCustomConfig( const QString &id, const QString &hostport )
2278{
2280
2281 QMutexLocker locker( mMutex.get() );
2282 if ( id.isEmpty() || hostport.isEmpty() )
2283 {
2284 QgsDebugError( QStringLiteral( "Passed config ID or host:port is empty" ) );
2285 return false;
2286 }
2287
2288 mCustomConfigByHostCache.clear();
2289
2290 // Loop through all storages with capability DeleteSslCertificateCustomConfig
2292
2293 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2294 {
2295 if ( storage->sslCertCustomConfigExists( id, hostport ) )
2296 {
2297 if ( !storage->removeSslCertCustomConfig( id, hostport ) )
2298 {
2299 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 );
2300 return false;
2301 }
2302 const QString shaHostPort( QStringLiteral( "%1:%2" ).arg( id, hostport ) );
2303 if ( mIgnoredSslErrorsCache.contains( shaHostPort ) )
2304 {
2305 mIgnoredSslErrorsCache.remove( shaHostPort );
2306 }
2307 return true;
2308 }
2309 }
2310
2311 if ( storages.empty() )
2312 {
2313 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2314 }
2315
2316 return false;
2317}
2318
2319
2321{
2323
2324 QMutexLocker locker( mMutex.get() );
2325 if ( !mIgnoredSslErrorsCache.isEmpty() )
2326 {
2327 QgsDebugMsgLevel( QStringLiteral( "Ignored SSL errors cache items:" ), 1 );
2328 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = mIgnoredSslErrorsCache.constBegin();
2329 while ( i != mIgnoredSslErrorsCache.constEnd() )
2330 {
2331 QStringList errs;
2332 for ( auto err : i.value() )
2333 {
2335 }
2336 QgsDebugMsgLevel( QStringLiteral( "%1 = %2" ).arg( i.key(), errs.join( ", " ) ), 1 );
2337 ++i;
2338 }
2339 }
2340 else
2341 {
2342 QgsDebugMsgLevel( QStringLiteral( "Ignored SSL errors cache EMPTY" ), 2 );
2343 }
2344}
2345
2347{
2349
2350 QMutexLocker locker( mMutex.get() );
2351 if ( config.isNull() )
2352 {
2353 QgsDebugError( QStringLiteral( "Passed config is null" ) );
2354 return false;
2355 }
2356
2357 QString shahostport( QStringLiteral( "%1:%2" )
2358 .arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ).trimmed(),
2359 config.sslHostPort().trimmed() ) );
2360 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2361 {
2362 mIgnoredSslErrorsCache.remove( shahostport );
2363 }
2364 const QList<QSslError::SslError> errenums( config.sslIgnoredErrorEnums() );
2365 if ( !errenums.isEmpty() )
2366 {
2367 mIgnoredSslErrorsCache.insert( shahostport, QSet<QSslError::SslError>( errenums.begin(), errenums.end() ) );
2368 QgsDebugMsgLevel( QStringLiteral( "Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1" ).arg( shahostport ), 2 );
2370 return true;
2371 }
2372
2373 QgsDebugMsgLevel( QStringLiteral( "No ignored SSL errors to cache for sha:host:port = %1" ).arg( shahostport ), 2 );
2374 return true;
2375}
2376
2377bool QgsAuthManager::updateIgnoredSslErrorsCache( const QString &shahostport, const QList<QSslError> &errors )
2378{
2380
2381 QMutexLocker locker( mMutex.get() );
2382 const thread_local QRegularExpression rx( QRegularExpression::anchoredPattern( "\\S+:\\S+:\\d+" ) );
2383 if ( !rx.match( shahostport ).hasMatch() )
2384 {
2385 QgsDebugError( "Passed shahostport does not match \\S+:\\S+:\\d+, "
2386 "e.g. 74a4ef5ea94512a43769b744cda0ca5049a72491:www.example.com:443" );
2387 return false;
2388 }
2389
2390 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2391 {
2392 mIgnoredSslErrorsCache.remove( shahostport );
2393 }
2394
2395 if ( errors.isEmpty() )
2396 {
2397 QgsDebugError( QStringLiteral( "Passed errors list empty" ) );
2398 return false;
2399 }
2400
2401 QSet<QSslError::SslError> errs;
2402 for ( const auto &error : errors )
2403 {
2404 if ( error.error() == QSslError::NoError )
2405 continue;
2406
2407 errs.insert( error.error() );
2408 }
2409
2410 if ( errs.isEmpty() )
2411 {
2412 QgsDebugError( QStringLiteral( "Passed errors list does not contain errors" ) );
2413 return false;
2414 }
2415
2416 mIgnoredSslErrorsCache.insert( shahostport, errs );
2417
2418 QgsDebugMsgLevel( QStringLiteral( "Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1" ).arg( shahostport ), 2 );
2420 return true;
2421}
2422
2424{
2426
2427 QMutexLocker locker( mMutex.get() );
2428 QHash<QString, QSet<QSslError::SslError> > prevcache( mIgnoredSslErrorsCache );
2429 QHash<QString, QSet<QSslError::SslError> > nextcache;
2430
2431 // Loop through all storages with capability ReadSslCertificateCustomConfig
2433
2434 QStringList ids;
2435
2436 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2437 {
2438 const auto customConfigs { storage->sslCertCustomConfigs() };
2439 for ( const auto &config : std::as_const( customConfigs ) )
2440 {
2441 const QString shaHostPort( QStringLiteral( "%1:%2" ).arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ), config.sslHostPort() ) );
2442 if ( ! ids.contains( shaHostPort ) )
2443 {
2444 ids.append( shaHostPort );
2445 if ( !config.sslIgnoredErrorEnums().isEmpty() )
2446 {
2447 nextcache.insert( shaHostPort, QSet<QSslError::SslError>( config.sslIgnoredErrorEnums().cbegin(), config.sslIgnoredErrorEnums().cend() ) );
2448 }
2449 if ( prevcache.contains( shaHostPort ) )
2450 {
2451 prevcache.remove( shaHostPort );
2452 }
2453 }
2454 else
2455 {
2456 emit messageLog( tr( "SSL custom config already in the list: %1" ).arg( config.sslHostPort() ), authManTag(), Qgis::MessageLevel::Warning );
2457 }
2458 }
2459 }
2460
2461 if ( !prevcache.isEmpty() )
2462 {
2463 // preserve any existing per-session ignored errors for hosts
2464 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = prevcache.constBegin();
2465 while ( i != prevcache.constEnd() )
2466 {
2467 nextcache.insert( i.key(), i.value() );
2468 ++i;
2469 }
2470 }
2471
2472 if ( nextcache != mIgnoredSslErrorsCache )
2473 {
2474 mIgnoredSslErrorsCache.clear();
2475 mIgnoredSslErrorsCache = nextcache;
2476 QgsDebugMsgLevel( QStringLiteral( "Rebuild of ignored SSL errors cache SUCCEEDED" ), 2 );
2478 return true;
2479 }
2480
2481 QgsDebugMsgLevel( QStringLiteral( "Rebuild of ignored SSL errors cache SAME AS BEFORE" ), 2 );
2483 return true;
2484}
2485
2486bool QgsAuthManager::storeCertAuthorities( const QList<QSslCertificate> &certs )
2487{
2489
2490 QMutexLocker locker( mMutex.get() );
2491 if ( certs.isEmpty() )
2492 {
2493 QgsDebugError( QStringLiteral( "Passed certificate list has no certs" ) );
2494 return false;
2495 }
2496
2497 for ( const auto &cert : certs )
2498 {
2499 if ( !storeCertAuthority( cert ) )
2500 return false;
2501 }
2502 return true;
2503}
2504
2505bool QgsAuthManager::storeCertAuthority( const QSslCertificate &cert )
2506{
2508
2509 QMutexLocker locker( mMutex.get() );
2510 // don't refuse !cert.isValid() (actually just expired) CAs,
2511 // as user may want to ignore that SSL connection error
2512 if ( cert.isNull() )
2513 {
2514 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2515 return false;
2516 }
2517
2518 if ( existsCertAuthority( cert ) && !removeCertAuthority( cert ) )
2519 {
2520 QgsDebugError( QStringLiteral( "Store certificate authority: FAILED to remove pre-existing certificate authority" ) );
2521 return false;
2522 }
2523
2525 {
2526 return defaultStorage->storeCertAuthority( cert );
2527 }
2528 else
2529 {
2530 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2531 return false;
2532 }
2533
2534 return false;
2535}
2536
2537const QSslCertificate QgsAuthManager::certAuthority( const QString &id )
2538{
2540
2541 QMutexLocker locker( mMutex.get() );
2542 QSslCertificate emptycert;
2543 QSslCertificate cert;
2544 if ( id.isEmpty() )
2545 return emptycert;
2546
2547 // Loop through all storages with capability ReadCertificateAuthority and get the certificate from the first one that has the certificate
2549
2550 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2551 {
2552 cert = storage->loadCertAuthority( id );
2553 if ( !cert.isNull() )
2554 {
2555 return cert;
2556 }
2557 }
2558
2559 if ( storages.empty() )
2560 {
2561 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2562 return emptycert;
2563 }
2564
2565 return cert;
2566}
2567
2568bool QgsAuthManager::existsCertAuthority( const QSslCertificate &cert )
2569{
2571
2572 QMutexLocker locker( mMutex.get() );
2573 if ( cert.isNull() )
2574 {
2575 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2576 return false;
2577 }
2578
2579 // Loop through all storages with capability ReadCertificateAuthority and get the certificate from the first one that has the certificate
2581
2582 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2583 {
2584 if ( storage->certAuthorityExists( cert ) )
2585 {
2586 return true;
2587 }
2588 }
2589
2590 if ( storages.empty() )
2591 {
2592 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2593 }
2594
2595 return false;
2596}
2597
2598bool QgsAuthManager::removeCertAuthority( const QSslCertificate &cert )
2599{
2601
2602 QMutexLocker locker( mMutex.get() );
2603 if ( cert.isNull() )
2604 {
2605 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2606 return false;
2607 }
2608
2609 // Loop through all storages with capability ReadCertificateAuthority and delete from the first one that has the certificate, fail if it has no capability
2611
2612 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2613 {
2614 if ( storage->certAuthorityExists( cert ) )
2615 {
2616
2618 {
2619 emit messageLog( tr( "Remove certificate: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
2620 return false;
2621 }
2622
2623 if ( !storage->removeCertAuthority( cert ) )
2624 {
2625 emit messageLog( tr( "Remove certificate authority: FAILED to remove certificate authority from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2626 return false;
2627 }
2628 return true;
2629 }
2630 }
2631
2632 if ( storages.empty() )
2633 {
2634 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2635 }
2636
2637 return false;
2638}
2639
2640const QList<QSslCertificate> QgsAuthManager::systemRootCAs()
2641{
2642 return QSslConfiguration::systemCaCertificates();
2643}
2644
2645const QList<QSslCertificate> QgsAuthManager::extraFileCAs()
2646{
2648
2649 QMutexLocker locker( mMutex.get() );
2650 QList<QSslCertificate> certs;
2651 QList<QSslCertificate> filecerts;
2652 QVariant cafileval = QgsAuthManager::instance()->authSetting( QStringLiteral( "cafile" ) );
2653 if ( QgsVariantUtils::isNull( cafileval ) )
2654 return certs;
2655
2656 QVariant allowinvalid = QgsAuthManager::instance()->authSetting( QStringLiteral( "cafileallowinvalid" ), QVariant( false ) );
2657 if ( QgsVariantUtils::isNull( allowinvalid ) )
2658 return certs;
2659
2660 QString cafile( cafileval.toString() );
2661 if ( !cafile.isEmpty() && QFile::exists( cafile ) )
2662 {
2663 filecerts = QgsAuthCertUtils::certsFromFile( cafile );
2664 }
2665 // only CAs or certs capable of signing other certs are allowed
2666 for ( const auto &cert : std::as_const( filecerts ) )
2667 {
2668 if ( !allowinvalid.toBool() && ( cert.isBlacklisted()
2669 || cert.isNull()
2670 || cert.expiryDate() <= QDateTime::currentDateTime()
2671 || cert.effectiveDate() > QDateTime::currentDateTime() ) )
2672 {
2673 continue;
2674 }
2675
2677 {
2678 certs << cert;
2679 }
2680 }
2681 return certs;
2682}
2683
2684const QList<QSslCertificate> QgsAuthManager::databaseCAs()
2685{
2687
2688 QMutexLocker locker( mMutex.get() );
2689
2690 // Loop through all storages with capability ReadCertificateAuthority and collect the certificates from all storages
2692
2693 QList<QSslCertificate> certs;
2694
2695 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2696 {
2697 const QList<QSslCertificate> storageCerts = storage->caCerts();
2698 // Add if not already in the list, warn otherwise
2699 for ( const QSslCertificate &cert : std::as_const( storageCerts ) )
2700 {
2701 if ( !certs.contains( cert ) )
2702 {
2703 certs.append( cert );
2704 }
2705 else
2706 {
2707 emit messageLog( tr( "Certificate already in the list: %1" ).arg( cert.issuerDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
2708 }
2709 }
2710 }
2711
2712 if ( storages.empty() )
2713 {
2714 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2715 }
2716
2717 return certs;
2718}
2719
2720const QMap<QString, QSslCertificate> QgsAuthManager::mappedDatabaseCAs()
2721{
2723
2724 QMutexLocker locker( mMutex.get() );
2726}
2727
2729{
2731
2732 QMutexLocker locker( mMutex.get() );
2733 mCaCertsCache.clear();
2734 // in reverse order of precedence, with regards to duplicates, so QMap inserts overwrite
2735 insertCaCertInCache( QgsAuthCertUtils::SystemRoot, systemRootCAs() );
2736 insertCaCertInCache( QgsAuthCertUtils::FromFile, extraFileCAs() );
2737 insertCaCertInCache( QgsAuthCertUtils::InDatabase, databaseCAs() );
2738
2739 bool res = !mCaCertsCache.isEmpty(); // should at least contain system root CAs
2740 if ( !res )
2741 QgsDebugError( QStringLiteral( "Rebuild of CA certs cache FAILED" ) );
2742 return res;
2743}
2744
2746{
2748
2749 QMutexLocker locker( mMutex.get() );
2750 if ( cert.isNull() )
2751 {
2752 QgsDebugError( QStringLiteral( "Passed certificate is null." ) );
2753 return false;
2754 }
2755
2756 if ( certTrustPolicy( cert ) == policy )
2757 {
2758 return true;
2759 }
2760
2762 {
2763 emit messageLog( tr( "Could not delete pre-existing certificate trust policy." ), authManTag(), Qgis::MessageLevel::Warning );
2764 return false;
2765 }
2766
2768 {
2769 return defaultStorage->storeCertTrustPolicy( cert, policy );
2770 }
2771 else
2772 {
2773 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
2774 return false;
2775 }
2776}
2777
2779{
2781
2782 QMutexLocker locker( mMutex.get() );
2783 if ( cert.isNull() )
2784 {
2785 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2787 }
2788
2789 // Loop through all storages with capability ReadCertificateTrustPolicy and get the policy from the first one that has the policy
2791
2792 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2793 {
2795 if ( policy != QgsAuthCertUtils::DefaultTrust )
2796 {
2797 return policy;
2798 }
2799 }
2800
2801 if ( storages.empty() )
2802 {
2803 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2804 }
2805
2807}
2808
2809bool QgsAuthManager::removeCertTrustPolicies( const QList<QSslCertificate> &certs )
2810{
2812
2813 QMutexLocker locker( mMutex.get() );
2814 if ( certs.empty() )
2815 {
2816 QgsDebugError( QStringLiteral( "Passed certificate list has no certs" ) );
2817 return false;
2818 }
2819
2820 for ( const auto &cert : certs )
2821 {
2822 if ( !removeCertTrustPolicy( cert ) )
2823 return false;
2824 }
2825 return true;
2826}
2827
2828bool QgsAuthManager::removeCertTrustPolicy( const QSslCertificate &cert )
2829{
2831
2832 QMutexLocker locker( mMutex.get() );
2833 if ( cert.isNull() )
2834 {
2835 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2836 return false;
2837 }
2838
2839 // Loop through all storages with capability ReadCertificateTrustPolicy and delete from the first one that has the policy, fail if it has no capability
2841
2842 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2843 {
2844 if ( storage->certTrustPolicyExists( cert ) )
2845 {
2847 {
2848 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 );
2849 return false;
2850 }
2851
2852 if ( !storage->removeCertTrustPolicy( cert ) )
2853 {
2854 emit messageLog( tr( "Remove certificate trust policy: FAILED to remove certificate trust policy from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2855 return false;
2856 }
2857 return true;
2858 }
2859 }
2860
2861 if ( storages.empty() )
2862 {
2863 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
2864 }
2865
2866 return false;
2867}
2868
2870{
2872
2873 QMutexLocker locker( mMutex.get() );
2874 if ( cert.isNull() )
2875 {
2877 }
2878
2879 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2880 const QStringList &trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
2881 const QStringList &untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
2882
2884 if ( trustedids.contains( id ) )
2885 {
2887 }
2888 else if ( untrustedids.contains( id ) )
2889 {
2891 }
2892 return policy;
2893}
2894
2896{
2898
2899 if ( policy == QgsAuthCertUtils::DefaultTrust )
2900 {
2901 // set default trust policy to Trusted by removing setting
2902 return removeAuthSetting( QStringLiteral( "certdefaulttrust" ) );
2903 }
2904 return storeAuthSetting( QStringLiteral( "certdefaulttrust" ), static_cast< int >( policy ) );
2905}
2906
2908{
2910
2911 QMutexLocker locker( mMutex.get() );
2912 QVariant policy( authSetting( QStringLiteral( "certdefaulttrust" ) ) );
2913 if ( QgsVariantUtils::isNull( policy ) )
2914 {
2916 }
2917 return static_cast< QgsAuthCertUtils::CertTrustPolicy >( policy.toInt() );
2918}
2919
2921{
2923
2924 QMutexLocker locker( mMutex.get() );
2925 mCertTrustCache.clear();
2926
2927 // Loop through all storages with capability ReadCertificateTrustPolicy
2929
2930 QStringList ids;
2931
2932 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2933 {
2934
2935 const auto trustedCerts { storage->caCertsPolicy() };
2936 for ( auto it = trustedCerts.cbegin(); it != trustedCerts.cend(); ++it )
2937 {
2938 const QString id { it.key( )};
2939 if ( ! ids.contains( id ) )
2940 {
2941 ids.append( id );
2942 const QgsAuthCertUtils::CertTrustPolicy policy( it.value() );
2944 {
2945 QStringList ids;
2946 if ( mCertTrustCache.contains( QgsAuthCertUtils::Trusted ) )
2947 {
2948 ids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
2949 }
2950 mCertTrustCache.insert( QgsAuthCertUtils::Trusted, ids << it.key() );
2951 }
2952 }
2953 else
2954 {
2955 emit messageLog( tr( "Certificate already in the list: %1" ).arg( it.key() ), authManTag(), Qgis::MessageLevel::Warning );
2956 }
2957 }
2958 }
2959
2960 if ( ! storages.empty() )
2961 {
2962 QgsDebugMsgLevel( QStringLiteral( "Rebuild of cert trust policy cache SUCCEEDED" ), 2 );
2963 return true;
2964 }
2965 else
2966 {
2967 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2968 return false;
2969 }
2970}
2971
2972const QList<QSslCertificate> QgsAuthManager::trustedCaCerts( bool includeinvalid )
2973{
2975
2976 QMutexLocker locker( mMutex.get() );
2978 QStringList trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
2979 QStringList untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
2980 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
2981
2982 QList<QSslCertificate> trustedcerts;
2983 for ( int i = 0; i < certpairs.size(); ++i )
2984 {
2985 QSslCertificate cert( certpairs.at( i ).second );
2986 QString certid( QgsAuthCertUtils::shaHexForCert( cert ) );
2987 if ( trustedids.contains( certid ) )
2988 {
2989 // trusted certs are always added regardless of their validity
2990 trustedcerts.append( cert );
2991 }
2992 else if ( defaultpolicy == QgsAuthCertUtils::Trusted && !untrustedids.contains( certid ) )
2993 {
2994 if ( !includeinvalid && !QgsAuthCertUtils::certIsViable( cert ) )
2995 continue;
2996 trustedcerts.append( cert );
2997 }
2998 }
2999
3000 // update application default SSL config for new requests
3001 QSslConfiguration sslconfig( QSslConfiguration::defaultConfiguration() );
3002 sslconfig.setCaCertificates( trustedcerts );
3003 QSslConfiguration::setDefaultConfiguration( sslconfig );
3004
3005 return trustedcerts;
3006}
3007
3008const QList<QSslCertificate> QgsAuthManager::untrustedCaCerts( QList<QSslCertificate> trustedCAs )
3009{
3011
3012 QMutexLocker locker( mMutex.get() );
3013 if ( trustedCAs.isEmpty() )
3014 {
3015 if ( mTrustedCaCertsCache.isEmpty() )
3016 {
3018 }
3019 trustedCAs = trustedCaCertsCache();
3020 }
3021
3022 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
3023
3024 QList<QSslCertificate> untrustedCAs;
3025 for ( int i = 0; i < certpairs.size(); ++i )
3026 {
3027 QSslCertificate cert( certpairs.at( i ).second );
3028 if ( !trustedCAs.contains( cert ) )
3029 {
3030 untrustedCAs.append( cert );
3031 }
3032 }
3033 return untrustedCAs;
3034}
3035
3037{
3039
3040 QMutexLocker locker( mMutex.get() );
3041 mTrustedCaCertsCache = trustedCaCerts();
3042 QgsDebugMsgLevel( QStringLiteral( "Rebuilt trusted cert authorities cache" ), 2 );
3043 // TODO: add some error trapping for the operation
3044 return true;
3045}
3046
3048{
3050
3051 QMutexLocker locker( mMutex.get() );
3053}
3054
3056{
3058
3059 QMutexLocker locker( mMutex.get() );
3060 if ( masterPasswordIsSet() )
3061 {
3062 return passwordHelperWrite( mMasterPass );
3063 }
3064 return false;
3065}
3066
3067
3069
3070#endif
3071
3073{
3075
3076 if ( isDisabled() )
3077 return;
3078
3079 const QStringList ids = configIds();
3080 for ( const auto &authcfg : ids )
3081 {
3082 clearCachedConfig( authcfg );
3083 }
3084}
3085
3086void QgsAuthManager::clearCachedConfig( const QString &authcfg )
3087{
3089
3090 if ( isDisabled() )
3091 return;
3092
3093 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
3094 if ( authmethod )
3095 {
3096 authmethod->clearCachedConfig( authcfg );
3097 }
3098}
3099
3100void QgsAuthManager::writeToConsole( const QString &message,
3101 const QString &tag,
3102 Qgis::MessageLevel level )
3103{
3104 Q_UNUSED( tag )
3105
3107
3108 // only output WARNING and CRITICAL messages
3109 if ( level == Qgis::MessageLevel::Info )
3110 return;
3111
3112 QString msg;
3113 switch ( level )
3114 {
3116 msg += QLatin1String( "WARNING: " );
3117 break;
3119 msg += QLatin1String( "ERROR: " );
3120 break;
3121 default:
3122 break;
3123 }
3124 msg += message;
3125
3126 QTextStream out( stdout, QIODevice::WriteOnly );
3127 out << msg << Qt::endl;
3128}
3129
3130void QgsAuthManager::tryToStartDbErase()
3131{
3133
3134 ++mScheduledDbEraseRequestCount;
3135 // wait a total of 90 seconds for GUI availiability or user interaction, then cancel schedule
3136 int trycutoff = 90 / ( mScheduledDbEraseRequestWait ? mScheduledDbEraseRequestWait : 3 );
3137 if ( mScheduledDbEraseRequestCount >= trycutoff )
3138 {
3140 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emitting/scheduling canceled" ), 2 );
3141 return;
3142 }
3143 else
3144 {
3145 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest attempt (%1 of %2)" )
3146 .arg( mScheduledDbEraseRequestCount ).arg( trycutoff ), 2 );
3147 }
3148
3149 if ( scheduledAuthDatabaseErase() && !mScheduledDbEraseRequestEmitted && mMutex->tryLock() )
3150 {
3151 // see note in header about this signal's use
3152 mScheduledDbEraseRequestEmitted = true;
3154
3155 mMutex->unlock();
3156
3157 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emitted" ), 2 );
3158 return;
3159 }
3160 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emit skipped" ), 2 );
3161}
3162
3163
3165{
3166 QMutexLocker locker( mMutex.get() );
3167
3168 QMapIterator<QThread *, QMetaObject::Connection> iterator( mConnectedThreads );
3169 while ( iterator.hasNext() )
3170 {
3171 iterator.next();
3172 QThread::disconnect( iterator.value() );
3173 }
3174
3175 if ( !mAuthInit )
3176 return;
3177
3178 locker.unlock();
3179
3180 if ( !isDisabled() )
3181 {
3183 qDeleteAll( mAuthMethods );
3184
3186 QSqlDatabase authConn = authDatabaseConnection();
3188 if ( authConn.isValid() && authConn.isOpen() )
3189 authConn.close();
3190 }
3191 delete mScheduledDbEraseTimer;
3192 mScheduledDbEraseTimer = nullptr;
3193 QSqlDatabase::removeDatabase( QStringLiteral( "authentication.configs" ) );
3194}
3195
3197{
3198 QMutexLocker locker( mMutex.get() );
3199 if ( ! mAuthConfigurationStorageRegistry )
3200 {
3201 mAuthConfigurationStorageRegistry = std::make_unique<QgsAuthConfigurationStorageRegistry>();
3202 }
3203 return mAuthConfigurationStorageRegistry.get();
3204}
3205
3206
3207QString QgsAuthManager::passwordHelperName() const
3208{
3209 return tr( "Password Helper" );
3210}
3211
3212
3213void QgsAuthManager::passwordHelperLog( const QString &msg ) const
3214{
3216
3218 {
3219 QgsMessageLog::logMessage( msg, passwordHelperName() );
3220 }
3221}
3222
3224{
3226
3227 passwordHelperLog( tr( "Opening %1 for DELETE…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3228 bool result;
3229 QKeychain::DeletePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3230 QgsSettings settings;
3231 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3232 job.setAutoDelete( false );
3233 job.setKey( authPasswordHelperKeyName() );
3234 QEventLoop loop;
3235 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3236 job.start();
3237 loop.exec();
3238 if ( job.error() )
3239 {
3240 mPasswordHelperErrorCode = job.error();
3241 mPasswordHelperErrorMessage = tr( "Delete password failed: %1." ).arg( job.errorString() );
3242 // Signals used in the tests to exit main application loop
3243 emit passwordHelperFailure();
3244 result = false;
3245 }
3246 else
3247 {
3248 // Signals used in the tests to exit main application loop
3249 emit passwordHelperSuccess();
3250 result = true;
3251 }
3252 passwordHelperProcessError();
3253 return result;
3254}
3255
3256QString QgsAuthManager::passwordHelperRead()
3257{
3259
3260 // Retrieve it!
3261 QString password;
3262 passwordHelperLog( tr( "Opening %1 for READ…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3263 QKeychain::ReadPasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3264 QgsSettings settings;
3265 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3266 job.setAutoDelete( false );
3267 job.setKey( authPasswordHelperKeyName() );
3268 QEventLoop loop;
3269 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3270 job.start();
3271 loop.exec();
3272 if ( job.error() )
3273 {
3274 mPasswordHelperErrorCode = job.error();
3275 mPasswordHelperErrorMessage = tr( "Retrieving password from your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() );
3276 // Signals used in the tests to exit main application loop
3277 emit passwordHelperFailure();
3278 }
3279 else
3280 {
3281 password = job.textData();
3282 // Password is there but it is empty, treat it like if it was not found
3283 if ( password.isEmpty() )
3284 {
3285 mPasswordHelperErrorCode = QKeychain::EntryNotFound;
3286 mPasswordHelperErrorMessage = tr( "Empty password retrieved from your %1." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME );
3287 // Signals used in the tests to exit main application loop
3288 emit passwordHelperFailure();
3289 }
3290 else
3291 {
3292 // Signals used in the tests to exit main application loop
3293 emit passwordHelperSuccess();
3294 }
3295 }
3296 passwordHelperProcessError();
3297 return password;
3298}
3299
3300bool QgsAuthManager::passwordHelperWrite( const QString &password )
3301{
3303
3304 Q_ASSERT( !password.isEmpty() );
3305 bool result;
3306 passwordHelperLog( tr( "Opening %1 for WRITE…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3307 QKeychain::WritePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3308 QgsSettings settings;
3309 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3310 job.setAutoDelete( false );
3311 job.setKey( authPasswordHelperKeyName() );
3312 job.setTextData( password );
3313 QEventLoop loop;
3314 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3315 job.start();
3316 loop.exec();
3317 if ( job.error() )
3318 {
3319 mPasswordHelperErrorCode = job.error();
3320 mPasswordHelperErrorMessage = tr( "Storing password in your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() );
3321 // Signals used in the tests to exit main application loop
3322 emit passwordHelperFailure();
3323 result = false;
3324 }
3325 else
3326 {
3327 passwordHelperClearErrors();
3328 // Signals used in the tests to exit main application loop
3329 emit passwordHelperSuccess();
3330 result = true;
3331 }
3332 passwordHelperProcessError();
3333 return result;
3334}
3335
3337{
3338 // Does the user want to store the password in the wallet?
3339 QgsSettings settings;
3340 return settings.value( QStringLiteral( "use_password_helper" ), true, QgsSettings::Section::Auth ).toBool();
3341}
3342
3344{
3345 QgsSettings settings;
3346 settings.setValue( QStringLiteral( "use_password_helper" ), enabled, QgsSettings::Section::Auth );
3347 emit messageLog( enabled ? tr( "Your %1 will be <b>used from now</b> on to store and retrieve the master password." )
3349 tr( "Your %1 will <b>not be used anymore</b> to store and retrieve the master password." )
3351}
3352
3354{
3355 // Does the user want to store the password in the wallet?
3356 QgsSettings settings;
3357 return settings.value( QStringLiteral( "password_helper_logging" ), false, QgsSettings::Section::Auth ).toBool();
3358}
3359
3361{
3362 QgsSettings settings;
3363 settings.setValue( QStringLiteral( "password_helper_logging" ), enabled, QgsSettings::Section::Auth );
3364}
3365
3366void QgsAuthManager::passwordHelperClearErrors()
3367{
3368 mPasswordHelperErrorCode = QKeychain::NoError;
3369 mPasswordHelperErrorMessage.clear();
3370}
3371
3372void QgsAuthManager::passwordHelperProcessError()
3373{
3375
3376 if ( mPasswordHelperErrorCode == QKeychain::AccessDenied ||
3377 mPasswordHelperErrorCode == QKeychain::AccessDeniedByUser ||
3378 mPasswordHelperErrorCode == QKeychain::NoBackendAvailable ||
3379 mPasswordHelperErrorCode == QKeychain::NotImplemented )
3380 {
3381 // If the error is permanent or the user denied access to the wallet
3382 // we also want to disable the wallet system to prevent annoying
3383 // notification on each subsequent access.
3384 setPasswordHelperEnabled( false );
3385 mPasswordHelperErrorMessage = tr( "There was an error and integration with your %1 system has been disabled. "
3386 "You can re-enable it at any time through the \"Utilities\" menu "
3387 "in the Authentication pane of the options dialog. %2" )
3388 .arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage );
3389 }
3390 if ( mPasswordHelperErrorCode != QKeychain::NoError )
3391 {
3392 // We've got an error from the wallet
3393 passwordHelperLog( tr( "Error in %1: %2" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage ) );
3394 emit passwordHelperMessageLog( mPasswordHelperErrorMessage, authManTag(), Qgis::MessageLevel::Critical );
3395 }
3396 passwordHelperClearErrors();
3397}
3398
3399
3400bool QgsAuthManager::masterPasswordInput()
3401{
3403
3404 if ( isDisabled() )
3405 return false;
3406
3407 QString pass;
3408 bool storedPasswordIsValid = false;
3409 bool ok = false;
3410
3411 // Read the password from the wallet
3412 if ( passwordHelperEnabled() )
3413 {
3414 pass = passwordHelperRead();
3415 if ( ! pass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) )
3416 {
3417 // Let's check the password!
3418 if ( verifyMasterPassword( pass ) )
3419 {
3420 ok = true;
3421 storedPasswordIsValid = true;
3422 emit passwordHelperMessageLog( tr( "Master password has been successfully read from your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), Qgis::MessageLevel::Info );
3423 }
3424 else
3425 {
3426 emit passwordHelperMessageLog( tr( "Master password stored in your %1 is not valid" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), Qgis::MessageLevel::Warning );
3427 }
3428 }
3429 }
3430
3431 if ( ! ok )
3432 {
3433 pass.clear();
3435 }
3436
3437 if ( ok && !pass.isEmpty() && mMasterPass != pass )
3438 {
3439 mMasterPass = pass;
3440 if ( passwordHelperEnabled() && ! storedPasswordIsValid )
3441 {
3442 if ( passwordHelperWrite( pass ) )
3443 {
3444 emit passwordHelperMessageLog( tr( "Master password has been successfully written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), Qgis::MessageLevel::Info );
3445 }
3446 else
3447 {
3448 emit passwordHelperMessageLog( tr( "Master password could not be written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), Qgis::MessageLevel::Warning );
3449 }
3450 }
3451 return true;
3452 }
3453 return false;
3454}
3455
3456bool QgsAuthManager::masterPasswordRowsInDb( int *rows ) const
3457{
3459
3460 if ( isDisabled() )
3461 return false;
3462
3463 *rows = 0;
3464
3465 QMutexLocker locker( mMutex.get() );
3466
3467 // Loop through all storages with capability ReadMasterPassword and count the number of master passwords
3469
3470 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3471 {
3472 try
3473 {
3474 *rows += storage->masterPasswords( ).count();
3475 }
3476 catch ( const QgsNotSupportedException &e )
3477 {
3478 // It should not happen because we are checking the capability in advance
3480 }
3481 }
3482
3483 if ( storages.empty() )
3484 {
3485 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3486 }
3487
3488 return rows != 0;
3489
3490}
3491
3493{
3495
3496 if ( isDisabled() )
3497 return false;
3498
3499 int rows = 0;
3500 if ( !masterPasswordRowsInDb( &rows ) )
3501 {
3502 const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
3503 QgsDebugError( err );
3505
3506 return false;
3507 }
3508 return ( rows == 1 );
3509}
3510
3511bool QgsAuthManager::masterPasswordCheckAgainstDb( const QString &compare ) const
3512{
3514
3515 if ( isDisabled() )
3516 return false;
3517
3518 // Only check the default DB
3519 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ReadMasterPassword ) )
3520 {
3521 try
3522 {
3523 const QList<QgsAuthConfigurationStorage::MasterPasswordConfig> passwords { defaultStorage->masterPasswords( ) };
3524 if ( passwords.size() == 0 )
3525 {
3526 emit messageLog( tr( "Master password: FAILED to access database" ), authManTag(), Qgis::MessageLevel::Critical );
3527 return false;
3528 }
3529 const QgsAuthConfigurationStorage::MasterPasswordConfig storedPassword { passwords.first() };
3530 return QgsAuthCrypto::verifyPasswordKeyHash( compare.isNull() ? mMasterPass : compare, storedPassword.salt, storedPassword.hash );
3531 }
3532 catch ( const QgsNotSupportedException &e )
3533 {
3534 // It should not happen because we are checking the capability in advance
3536 return false;
3537 }
3538
3539 }
3540 else
3541 {
3542 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3543 return false;
3544 }
3545}
3546
3547bool QgsAuthManager::masterPasswordStoreInDb() const
3548{
3550
3551 if ( isDisabled() )
3552 return false;
3553
3554 QString salt, hash, civ;
3555 QgsAuthCrypto::passwordKeyHash( mMasterPass, &salt, &hash, &civ );
3556
3557 // Only store in the default DB
3558 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateMasterPassword ) )
3559 {
3560 try
3561 {
3562 return defaultStorage->storeMasterPassword( { salt, civ, hash } );
3563 }
3564 catch ( const QgsNotSupportedException &e )
3565 {
3566 // It should not happen because we are checking the capability in advance
3568 return false;
3569 }
3570 }
3571 else
3572 {
3573 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3574 return false;
3575 }
3576}
3577
3578bool QgsAuthManager::masterPasswordClearDb()
3579{
3581
3582 if ( isDisabled() )
3583 return false;
3584
3585 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::DeleteMasterPassword ) )
3586 {
3587
3588 try
3589 {
3590 return defaultStorage->clearMasterPasswords();
3591 }
3592 catch ( const QgsNotSupportedException &e )
3593 {
3594 // It should not happen because we are checking the capability in advance
3596 return false;
3597 }
3598
3599 }
3600 else
3601 {
3602 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3603 return false;
3604 }
3605}
3606
3607const QString QgsAuthManager::masterPasswordCiv() const
3608{
3610
3611 if ( isDisabled() )
3612 return QString();
3613
3614 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ReadMasterPassword ) )
3615 {
3616 try
3617 {
3618 const QList<QgsAuthConfigurationStorage::MasterPasswordConfig> passwords { defaultStorage->masterPasswords( ) };
3619 if ( passwords.size() == 0 )
3620 {
3621 emit messageLog( tr( "Master password: FAILED to access database" ), authManTag(), Qgis::MessageLevel::Critical );
3622 return QString();
3623 }
3624 return passwords.first().civ;
3625 }
3626 catch ( const QgsNotSupportedException &e )
3627 {
3628 // It should not happen because we are checking the capability in advance
3630 return QString();
3631 }
3632 }
3633 else
3634 {
3635 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3636 return QString();
3637 }
3638}
3639
3640QStringList QgsAuthManager::configIds() const
3641{
3643
3644 QStringList configKeys = QStringList();
3645
3646 if ( isDisabled() )
3647 return configKeys;
3648
3649 // Loop through all storages with capability ReadConfiguration and get the config ids
3651
3652 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3653 {
3654 try
3655 {
3656 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
3657 // Check if the config ids are already in the list
3658 for ( auto it = configs.cbegin(); it != configs.cend(); ++it )
3659 {
3660 if ( !configKeys.contains( it.key() ) )
3661 {
3662 configKeys.append( it.key() );
3663 }
3664 else
3665 {
3666 emit messageLog( tr( "Config id %1 is already in the list" ).arg( it.key() ), authManTag(), Qgis::MessageLevel::Warning );
3667 }
3668 }
3669 }
3670 catch ( const QgsNotSupportedException &e )
3671 {
3672 // It should not happen because we are checking the capability in advance
3674 }
3675 }
3676
3677 return configKeys;
3678}
3679
3680bool QgsAuthManager::verifyPasswordCanDecryptConfigs() const
3681{
3683
3684 if ( isDisabled() )
3685 return false;
3686
3687 // no need to check for setMasterPassword, since this is private and it will be set
3688
3689 // Loop through all storages with capability ReadConfiguration and check if the password can decrypt the configs
3691
3692 for ( const QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3693 {
3694
3695 if ( ! storage->isEncrypted() )
3696 {
3697 continue;
3698 }
3699
3700 try
3701 {
3702 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigsWithPayload();
3703 for ( auto it = configs.cbegin(); it != configs.cend(); ++it )
3704 {
3705 QString configstring( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), it.value().config( QStringLiteral( "encrypted_payload" ) ) ) );
3706 if ( configstring.isEmpty() )
3707 {
3708 QgsDebugError( QStringLiteral( "Verify password can decrypt configs FAILED, could not decrypt a config (id: %1) from storage %2" )
3709 .arg( it.key(), storage->name() ) );
3710 return false;
3711 }
3712 }
3713 }
3714 catch ( const QgsNotSupportedException &e )
3715 {
3716 // It should not happen because we are checking the capability in advance
3718 return false;
3719 }
3720
3721 }
3722
3723 if ( storages.empty() )
3724 {
3725 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3726 return false;
3727 }
3728
3729 return true;
3730}
3731
3732bool QgsAuthManager::reencryptAllAuthenticationConfigs( const QString &prevpass, const QString &prevciv )
3733{
3735
3736 if ( isDisabled() )
3737 return false;
3738
3739 bool res = true;
3740 const QStringList ids = configIds();
3741 for ( const auto &configid : ids )
3742 {
3743 res = res && reencryptAuthenticationConfig( configid, prevpass, prevciv );
3744 }
3745 return res;
3746}
3747
3748bool QgsAuthManager::reencryptAuthenticationConfig( const QString &authcfg, const QString &prevpass, const QString &prevciv )
3749{
3751
3752 if ( isDisabled() )
3753 return false;
3754
3755 // no need to check for setMasterPassword, since this is private and it will be set
3756
3757 // Loop through all storages with capability ReadConfiguration and reencrypt the config
3759
3760 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3761 {
3762 try
3763 {
3764 if ( storage->methodConfigExists( authcfg ) )
3765 {
3766 if ( ! storage->isEncrypted() )
3767 {
3768 return true;
3769 }
3770
3771 QString payload;
3772 const QgsAuthMethodConfig config = storage->loadMethodConfig( authcfg, payload, true );
3773 if ( payload.isEmpty() || ! config.isValid( true ) )
3774 {
3775 QgsDebugError( QStringLiteral( "Reencrypt FAILED, could not find config (id: %1)" ).arg( authcfg ) );
3776 return false;
3777 }
3778
3779 QString configstring( QgsAuthCrypto::decrypt( prevpass, prevciv, payload ) );
3780 if ( configstring.isEmpty() )
3781 {
3782 QgsDebugError( QStringLiteral( "Reencrypt FAILED, could not decrypt config (id: %1)" ).arg( authcfg ) );
3783 return false;
3784 }
3785
3786 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
3787
3788 if ( !storage->storeMethodConfig( config, configstring ) )
3789 {
3790 emit messageLog( tr( "Store config: FAILED to store config in default storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
3791 return false;
3792 }
3793 return true;
3794 }
3795 }
3796 catch ( const QgsNotSupportedException &e )
3797 {
3798 // It should not happen because we are checking the capability in advance
3800 return false;
3801 }
3802 }
3803
3804 if ( storages.empty() )
3805 {
3806 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3807 }
3808 else
3809 {
3810 emit messageLog( tr( "Reencrypt FAILED, could not find config (id: %1)" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
3811 }
3812
3813 return false;
3814}
3815
3816bool QgsAuthManager::reencryptAllAuthenticationSettings( const QString &prevpass, const QString &prevciv )
3817{
3819
3820 // TODO: start remove (when function is actually used)
3821 Q_UNUSED( prevpass )
3822 Q_UNUSED( prevciv )
3823 return true;
3824 // end remove
3825
3826#if 0
3827 if ( isDisabled() )
3828 return false;
3829
3831 // When adding settings that require encryption, add to list //
3833
3834 QStringList encryptedsettings;
3835 encryptedsettings << "";
3836
3837 for ( const auto & sett, std::as_const( encryptedsettings ) )
3838 {
3839 if ( sett.isEmpty() || !existsAuthSetting( sett ) )
3840 continue;
3841
3842 // no need to check for setMasterPassword, since this is private and it will be set
3843
3844 QSqlQuery query( authDbConnection() );
3845
3846 query.prepare( QStringLiteral( "SELECT value FROM %1 "
3847 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
3848
3849 query.bindValue( ":setting", sett );
3850
3851 if ( !authDbQuery( &query ) )
3852 return false;
3853
3854 if ( !query.isActive() || !query.isSelect() )
3855 {
3856 QgsDebugError( QStringLiteral( "Reencrypt FAILED, query not active or a select operation for setting: %2" ).arg( sett ) );
3857 return false;
3858 }
3859
3860 if ( query.first() )
3861 {
3862 QString settvalue( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
3863
3864 query.clear();
3865
3866 query.prepare( QStringLiteral( "UPDATE %1 "
3867 "SET value = :value "
3868 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
3869
3870 query.bindValue( ":setting", sett );
3871 query.bindValue( ":value", QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), settvalue ) );
3872
3873 if ( !authDbStartTransaction() )
3874 return false;
3875
3876 if ( !authDbQuery( &query ) )
3877 return false;
3878
3879 if ( !authDbCommit() )
3880 return false;
3881
3882 QgsDebugMsgLevel( QStringLiteral( "Reencrypt SUCCESS for setting: %2" ).arg( sett ), 2 );
3883 return true;
3884 }
3885 else
3886 {
3887 QgsDebugError( QStringLiteral( "Reencrypt FAILED, could not find in db setting: %2" ).arg( sett ) );
3888 return false;
3889 }
3890
3891 if ( query.next() )
3892 {
3893 QgsDebugError( QStringLiteral( "Select contains more than one for setting: %1" ).arg( sett ) );
3894 emit messageOut( tr( "Authentication database contains duplicate setting keys" ), authManTag(), WARNING );
3895 }
3896
3897 return false;
3898 }
3899
3900 return true;
3901#endif
3902}
3903
3904bool QgsAuthManager::reencryptAllAuthenticationIdentities( const QString &prevpass, const QString &prevciv )
3905{
3907
3908 if ( isDisabled() )
3909 return false;
3910
3911 bool res = true;
3912 const QStringList ids = certIdentityIds();
3913 for ( const auto &identid : ids )
3914 {
3915 res = res && reencryptAuthenticationIdentity( identid, prevpass, prevciv );
3916 }
3917 return res;
3918}
3919
3920bool QgsAuthManager::reencryptAuthenticationIdentity(
3921 const QString &identid,
3922 const QString &prevpass,
3923 const QString &prevciv )
3924{
3926
3927 if ( isDisabled() )
3928 return false;
3929
3930 // no need to check for setMasterPassword, since this is private and it will be set
3931
3932 // Loop through all storages with capability ReadCertificateIdentity and reencrypt the identity
3934
3935
3936 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3937 {
3938
3939 try
3940 {
3941
3942 if ( storage->certIdentityExists( identid ) )
3943 {
3944 if ( ! storage->isEncrypted() )
3945 {
3946 return true;
3947 }
3948
3949 const QPair<QSslCertificate, QString> identityBundle = storage->loadCertIdentityBundle( identid );
3950 QString keystring( QgsAuthCrypto::decrypt( prevpass, prevciv, identityBundle.second ) );
3951 if ( keystring.isEmpty() )
3952 {
3953 QgsDebugError( QStringLiteral( "Reencrypt FAILED, could not decrypt identity id: %1" ).arg( identid ) );
3954 return false;
3955 }
3956
3957 keystring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), keystring );
3958 return storage->storeCertIdentity( identityBundle.first, keystring );
3959 }
3960 }
3961 catch ( const QgsNotSupportedException &e )
3962 {
3963 // It should not happen because we are checking the capability in advance
3965 return false;
3966 }
3967 }
3968
3969 if ( storages.empty() )
3970 {
3971 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3972 }
3973 else
3974 {
3975 emit messageLog( tr( "Reencrypt FAILED, could not find identity (id: %1)" ).arg( identid ), authManTag(), Qgis::MessageLevel::Critical );
3976 }
3977
3978 return false;
3979}
3980
3981void QgsAuthManager::insertCaCertInCache( QgsAuthCertUtils::CaCertSource source, const QList<QSslCertificate> &certs )
3982{
3984
3985 for ( const auto &cert : certs )
3986 {
3987 mCaCertsCache.insert( QgsAuthCertUtils::shaHexForCert( cert ),
3988 QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate>( source, cert ) );
3989 }
3990}
3991
3992QString QgsAuthManager::authPasswordHelperKeyName() const
3993{
3995
3996 QString dbProfilePath;
3997
3998 // TODO: get the current profile name from the application
3999
4000 if ( isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
4001 {
4002 const QFileInfo info( mAuthDatabaseConnectionUri );
4003 dbProfilePath = info.dir().dirName();
4004 }
4005 else
4006 {
4007 dbProfilePath = QCryptographicHash::hash( ( mAuthDatabaseConnectionUri.toUtf8() ), QCryptographicHash::Md5 ).toHex();
4008 }
4009
4010 // if not running from the default profile, ensure that a different key is used
4011 return AUTH_PASSWORD_HELPER_KEY_NAME_BASE + ( dbProfilePath.compare( QLatin1String( "default" ), Qt::CaseInsensitive ) == 0 ? QString() : dbProfilePath );
4012}
4013
4015{
4017 const auto storages = storageRegistry->readyStorages( );
4018 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4019 {
4020 if ( qobject_cast<QgsAuthConfigurationStorageDb *>( storage ) )
4021 {
4022 return static_cast<QgsAuthConfigurationStorageDb *>( storage );
4023 }
4024 }
4025 return nullptr;
4026}
4027
4028QgsAuthConfigurationStorage *QgsAuthManager::firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability capability ) const
4029{
4031 return storageRegistry->firstReadyStorageWithCapability( capability );
4032}
4033
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:154
@ Warning
Warning message.
Definition qgis.h:156
@ Critical
Critical/error message.
Definition qgis.h:157
@ Info
Information message.
Definition qgis.h:155
AuthConfigurationStorageCapability
Authentication configuration storage capabilities.
Definition qgis.h:100
@ CreateSetting
Can create a new authentication setting.
@ CreateConfiguration
Can create a new authentication configuration.
@ ClearStorage
Can clear all configurations from storage.
@ DeleteCertificateAuthority
Can delete a certificate authority.
@ DeleteSslCertificateCustomConfig
Can delete a SSL certificate custom config.
@ DeleteSetting
Can delete the authentication setting.
@ ReadSslCertificateCustomConfig
Can read a SSL certificate custom config.
@ DeleteMasterPassword
Can delete the master password.
@ CreateSslCertificateCustomConfig
Can create a new SSL certificate custom config.
@ ReadCertificateTrustPolicy
Can read a certificate trust policy.
@ ReadConfiguration
Can read an authentication configuration.
@ UpdateConfiguration
Can update an authentication configuration.
@ ReadCertificateAuthority
Can read a certificate authority.
@ CreateCertificateAuthority
Can create a new certificate authority.
@ DeleteConfiguration
Can deleet an authentication configuration.
@ ReadSetting
Can read the authentication settings.
@ CreateCertificateIdentity
Can create a new certificate identity.
@ ReadCertificateIdentity
Can read a certificate identity.
@ CreateCertificateTrustPolicy
Can create a new certificate trust policy.
@ ReadMasterPassword
Can read the master password.
@ CreateMasterPassword
Can create a new master password.
@ DeleteCertificateTrustPolicy
Can delete a certificate trust policy.
static QString sslErrorEnumString(QSslError::SslError errenum)
Gets short strings describing an SSL error.
static QString shaHexForCert(const QSslCertificate &cert, bool formatted=false)
Gets the sha1 hash for certificate.
CertTrustPolicy
Type of certificate trust policy.
static QMap< QString, QSslCertificate > mapDigestToCerts(const QList< QSslCertificate > &certs)
Map certificate sha1 to certificate as simple cache.
static QByteArray certsToPemText(const QList< QSslCertificate > &certs)
certsToPemText dump a list of QSslCertificates to PEM text
static bool certIsViable(const QSslCertificate &cert)
certIsViable checks for viability errors of cert and whether it is NULL
static QList< QSslCertificate > certsFromFile(const QString &certspath)
Returns a list of concatenated certs from a PEM or DER formatted file.
static bool certificateIsAuthorityOrIssuer(const QSslCertificate &cert)
Gets whether a certificate is an Authority or can at least sign other certificates.
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...
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.
void messageLog(const QString &message, const QString &tag=QStringLiteral("Authentication"), Qgis::MessageLevel level=Qgis::MessageLevel::Info)
Custom logging signal to relay to console output and QgsMessageLog.
virtual QString lastError() const
Returns the last error message.
static void passwordKeyHash(const QString &pass, QString *salt, QString *hash, QString *cipheriv=nullptr)
Generate SHA256 hash for master password, with iterations and salt.
static const QString encrypt(const QString &pass, const QString &cipheriv, const QString &text)
Encrypt data using master password.
static bool verifyPasswordKeyHash(const QString &pass, const QString &salt, const QString &hash, QString *hashderived=nullptr)
Verify existing master password hash to a re-generated one.
static const QString decrypt(const QString &pass, const QString &cipheriv, const QString &text)
Decrypt data using master password.
Singleton offering an interface to manage the authentication configuration database and to utilize co...
bool storeAuthSetting(const QString &key, const QVariant &value, bool encrypt=false)
Store an authentication setting (stored as string via QVariant( value ).toString() )
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.
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)
authSetting get an authentication setting (retrieved as string and returned as QVariant( QString ))
static const QString AUTH_MAN_TAG
The display name of the Authentication Manager.
QgsAuthCertUtils::CertTrustPolicy defaultCertTrustPolicy()
Gets the default certificate trust policy preferred by user.
const QByteArray trustedCaCertsPemText()
trustedCaCertsPemText get concatenated string of all trusted CA certificates
static bool hasConfigId(const QString &txt)
Returns whether a string includes an authcfg ID token.
bool removeAllAuthenticationConfigs()
Clear all authentication configs from table in database and from provider caches.
QgsAuthCertUtils::CertTrustPolicy certificateTrustPolicy(const QSslCertificate &cert)
certificateTrustPolicy get trust policy for a particular certificate cert
static bool passwordHelperLoggingEnabled()
Password helper logging enabled getter.
QgsAuthConfigurationStorageRegistry * authConfigurationStorageRegistry() const
Returns the authentication configuration storage registry.
bool rebuildCertTrustCache()
Rebuild certificate authority cache.
Q_DECL_DEPRECATED const QString authenticationDatabasePath() const
The standard authentication database file in ~/.qgis3/ or defined location.
static const QList< QSslCertificate > systemRootCAs()
systemRootCAs get root system certificate authorities
bool removeCertAuthority(const QSslCertificate &cert)
Remove a certificate authority.
const QList< QSslCertificate > trustedCaCerts(bool includeinvalid=false)
trustedCaCerts get list of all trusted CA certificates
bool existsCertAuthority(const QSslCertificate &cert)
Check if a certificate authority exists.
const QMap< QString, QSslCertificate > mappedDatabaseCAs()
mappedDatabaseCAs get sha1-mapped database-stored certificate authorities
bool importAuthenticationConfigsFromXml(const QString &filename, const QString &password=QString(), bool overwrite=false)
Import authentication configurations from an XML file.
bool configIdUnique(const QString &id) const
Verify if provided authentication id is unique.
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.
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.
static QgsAuthManager * instance()
Enforce singleton pattern.
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.
bool resetMasterPassword(const QString &newpass, const QString &oldpass, bool keepbackup, QString *backuppath=nullptr)
Reset the master password to a new one, then re-encrypt all previous configs in a new database file,...
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,...
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.
static const QString AUTH_PASSWORD_HELPER_DISPLAY_NAME
The display name of the password helper (platform dependent)
bool updateNetworkReply(QNetworkReply *reply, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkReply with an authentication config (used to skip known SSL errors,...
bool rebuildTrustedCaCertsCache()
Rebuild trusted certificate authorities cache.
const QgsAuthMethodMetadata * authMethodMetadata(const QString &authMethodKey)
Gets authentication method metadata via its key.
bool removeAuthenticationConfig(const QString &authcfg)
Remove an authentication config in the database.
bool removeCertTrustPolicy(const QSslCertificate &cert)
Remove a certificate authority.
const QString authenticationDatabaseUriStripped() const
Returns the authentication database connection URI with the password stripped.
QgsAuthMethod::Expansions supportedAuthMethodExpansions(const QString &authcfg)
Gets supported authentication method expansion(s), e.g.
const QgsAuthConfigSslServer sslCertCustomConfig(const QString &id, const QString &hostport)
sslCertCustomConfig get an SSL certificate custom config by id (sha hash) and hostport (host:port)
QgsAuthMethodConfigsMap availableAuthMethodConfigs(const QString &dataprovider=QString())
Gets mapping of authentication config ids and their base configs (not decrypted data)
bool masterPasswordSame(const QString &password) const
Check whether supplied password is the same as the one already set.
bool storeAuthenticationConfig(QgsAuthMethodConfig &mconfig, bool overwrite=false)
Store an authentication config in the database.
bool removeCertIdentity(const QString &id)
Remove a certificate identity.
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.
A registry / canonical manager of authentication methods.
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.
QgsAuthMethod * createAuthMethod(const QString &authMethodKey)
Create an instance of the auth method.
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)
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.
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6535
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6534
QHash< QString, QgsAuthMethodConfig > QgsAuthMethodConfigsMap
QHash< QString, QgsAuthMethod * > QgsAuthMethodsMap
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
Structure that holds the (encrypted) master password elements.