QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
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 return true;
361}
362
363void QgsAuthManager::setup( const QString &pluginPath, const QString &authDatabasePath )
364{
365 mPluginPath = pluginPath;
366 mAuthDatabaseConnectionUri = authDatabasePath;
367}
368
370{
372
373 if ( mAuthDisabled )
374 {
375 QgsDebugError( QStringLiteral( "Authentication system DISABLED: QCA's qca-ossl (OpenSSL) plugin is missing" ) );
376 }
377 return mAuthDisabled;
378}
379
381{
383
384 return tr( "Authentication system is DISABLED:\n%1" ).arg( mAuthDisabledMessage );
385}
386
387
388const QString QgsAuthManager::sqliteDatabasePath() const
389{
390 if ( !QgsAuthManager::isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
391 {
392 return QString();
393 }
394
395 // Remove the driver:// prefix if present
396 QString path = mAuthDatabaseConnectionUri;
397 if ( path.startsWith( QStringLiteral( "QSQLITE://" ), Qt::CaseSensitivity::CaseInsensitive ) )
398 {
399 path = path.mid( 10 );
400 }
401 else if ( path.startsWith( QStringLiteral( "QSPATIALITE://" ), Qt::CaseSensitivity::CaseInsensitive ) )
402 {
403 path = path.mid( 14 );
404 }
405
406 return QDir::cleanPath( path );
407}
408
410{
411 return sqliteDatabasePath();
412}
413
415{
417
418 QMutexLocker locker( mMasterPasswordMutex.get() );
419 if ( isDisabled() )
420 return false;
421
422 if ( mScheduledDbErase )
423 return false;
424
425 if ( mMasterPass.isEmpty() )
426 {
427 QgsDebugMsgLevel( QStringLiteral( "Master password is not yet set by user" ), 2 );
428 if ( !masterPasswordInput() )
429 {
430 QgsDebugMsgLevel( QStringLiteral( "Master password input canceled by user" ), 2 );
431 return false;
432 }
433 }
434 else
435 {
436 QgsDebugMsgLevel( QStringLiteral( "Master password is set" ), 2 );
437 if ( !verify )
438 return true;
439 }
440
441 if ( !verifyMasterPassword() )
442 return false;
443
444 QgsDebugMsgLevel( QStringLiteral( "Master password is set and verified" ), 2 );
445 return true;
446}
447
448bool QgsAuthManager::setMasterPassword( const QString &pass, bool verify )
449{
451
452 QMutexLocker locker( mMutex.get() );
453 if ( isDisabled() )
454 return false;
455
456 if ( mScheduledDbErase )
457 return false;
458
459 // since this is generally for automation, we don't care if passed-in is same as existing
460 QString prevpass = QString( mMasterPass );
461 mMasterPass = pass;
462 if ( verify && !verifyMasterPassword() )
463 {
464 mMasterPass = prevpass;
465 const char *err = QT_TR_NOOP( "Master password set: FAILED to verify, reset to previous" );
466 QgsDebugError( err );
468 return false;
469 }
470
471 QgsDebugMsgLevel( QStringLiteral( "Master password set: SUCCESS%1" ).arg( verify ? " and verified" : "" ), 2 );
472 return true;
473}
474
475bool QgsAuthManager::verifyMasterPassword( const QString &compare )
476{
478
479 if ( isDisabled() )
480 return false;
481
482 int rows = 0;
483 if ( !masterPasswordRowsInDb( &rows ) )
484 {
485 const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
486 QgsDebugError( err );
488
490 return false;
491 }
492
493 QgsDebugMsgLevel( QStringLiteral( "Master password: %1 rows in database" ).arg( rows ), 2 );
494
495 if ( rows > 1 )
496 {
497 const char *err = QT_TR_NOOP( "Master password: FAILED to find just one master password record in database" );
498 QgsDebugError( err );
500
502 return false;
503 }
504 else if ( rows == 1 )
505 {
506 if ( !masterPasswordCheckAgainstDb( compare ) )
507 {
508 if ( compare.isNull() ) // don't complain when comparing, since it could be an incomplete comparison string
509 {
510 const char *err = QT_TR_NOOP( "Master password: FAILED to verify against hash in database" );
511 QgsDebugError( err );
513
515
516 emit masterPasswordVerified( false );
517 }
518 ++mPassTries;
519 if ( mPassTries >= 5 )
520 {
521 mAuthDisabled = true;
522 const char *err = QT_TR_NOOP( "Master password: failed 5 times authentication system DISABLED" );
523 QgsDebugError( err );
525 }
526 return false;
527 }
528 else
529 {
530 QgsDebugMsgLevel( QStringLiteral( "Master password: verified against hash in database" ), 2 );
531 if ( compare.isNull() )
532 emit masterPasswordVerified( true );
533 }
534 }
535 else if ( compare.isNull() ) // compares should never be stored
536 {
537 if ( !masterPasswordStoreInDb() )
538 {
539 const char *err = QT_TR_NOOP( "Master password: hash FAILED to be stored in database" );
540 QgsDebugError( err );
542
544 return false;
545 }
546 else
547 {
548 QgsDebugMsgLevel( QStringLiteral( "Master password: hash stored in database" ), 2 );
549 }
550 // double-check storing
551 if ( !masterPasswordCheckAgainstDb() )
552 {
553 const char *err = QT_TR_NOOP( "Master password: FAILED to verify against hash in database" );
554 QgsDebugError( err );
556
558 emit masterPasswordVerified( false );
559 return false;
560 }
561 else
562 {
563 QgsDebugMsgLevel( QStringLiteral( "Master password: verified against hash in database" ), 2 );
564 emit masterPasswordVerified( true );
565 }
566 }
567
568 return true;
569}
570
572{
574
575 return !mMasterPass.isEmpty();
576}
577
578bool QgsAuthManager::masterPasswordSame( const QString &pass ) const
579{
581
582 return mMasterPass == pass;
583}
584
585bool QgsAuthManager::resetMasterPassword( const QString &newpass, const QString &oldpass,
586 bool keepbackup, QString *backuppath )
587{
589
590 if ( isDisabled() )
591 return false;
592
593 // verify caller knows the current master password
594 // this means that the user will have had to already set the master password as well
595 if ( !masterPasswordSame( oldpass ) )
596 return false;
597
598 QString dbbackup;
599 if ( !backupAuthenticationDatabase( &dbbackup ) )
600 return false;
601
602 QgsDebugMsgLevel( QStringLiteral( "Master password reset: backed up current database" ), 2 );
603
604 // store current password and civ
605 QString prevpass = QString( mMasterPass );
606 QString prevciv = QString( masterPasswordCiv() );
607
608 // on ANY FAILURE from this point, reinstate previous password and database
609 bool ok = true;
610
611 // clear password hash table (also clears mMasterPass)
612 if ( ok && !masterPasswordClearDb() )
613 {
614 ok = false;
615 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not clear current password from database" );
616 QgsDebugError( err );
618 }
619 if ( ok )
620 {
621 QgsDebugMsgLevel( QStringLiteral( "Master password reset: cleared current password from database" ), 2 );
622 }
623
624 // mMasterPass empty, set new password (don't verify, since not stored yet)
625 setMasterPassword( newpass, false );
626
627 // store new password hash
628 if ( ok && !masterPasswordStoreInDb() )
629 {
630 ok = false;
631 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not store new password in database" );
632 QgsDebugError( err );
634 }
635 if ( ok )
636 {
637 QgsDebugMsgLevel( QStringLiteral( "Master password reset: stored new password in database" ), 2 );
638 }
639
640 // verify it stored password properly
641 if ( ok && !verifyMasterPassword() )
642 {
643 ok = false;
644 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not verify new password in database" );
645 QgsDebugError( err );
647 }
648
649 // re-encrypt everything with new password
650 if ( ok && !reencryptAllAuthenticationConfigs( prevpass, prevciv ) )
651 {
652 ok = false;
653 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt configs in database" );
654 QgsDebugError( err );
656 }
657 if ( ok )
658 {
659 QgsDebugMsgLevel( QStringLiteral( "Master password reset: re-encrypted configs in database" ), 2 );
660 }
661
662 // verify it all worked
663 if ( ok && !verifyPasswordCanDecryptConfigs() )
664 {
665 ok = false;
666 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not verify password can decrypt re-encrypted configs" );
667 QgsDebugError( err );
669 }
670
671 if ( ok && !reencryptAllAuthenticationSettings( prevpass, prevciv ) )
672 {
673 ok = false;
674 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt settings in database" );
675 QgsDebugError( err );
677 }
678
679 if ( ok && !reencryptAllAuthenticationIdentities( prevpass, prevciv ) )
680 {
681 ok = false;
682 const char *err = QT_TR_NOOP( "Master password reset FAILED: could not re-encrypt identities in database" );
683 QgsDebugError( err );
685 }
686
687 // something went wrong, reinstate previous password and database
688 if ( !ok )
689 {
690 // backup database of failed attempt, for inspection
691 QString errdbbackup( dbbackup );
692 errdbbackup.replace( QLatin1String( ".db" ), QLatin1String( "_ERROR.db" ) );
693 QFile::rename( sqliteDatabasePath(), errdbbackup );
694 QgsDebugError( QStringLiteral( "Master password reset FAILED: backed up failed db at %1" ).arg( errdbbackup ) );
695 // reinstate previous database and password
696 QFile::rename( dbbackup, sqliteDatabasePath() );
697 mMasterPass = prevpass;
698 QgsDebugError( QStringLiteral( "Master password reset FAILED: reinstated previous password and database" ) );
699
700 // assign error db backup
701 if ( backuppath )
702 *backuppath = errdbbackup;
703
704 return false;
705 }
706
707
708 if ( !keepbackup && !QFile::remove( dbbackup ) )
709 {
710 const char *err = QT_TR_NOOP( "Master password reset: could not remove old database backup" );
711 QgsDebugError( err );
713 // a non-blocking error, continue
714 }
715
716 if ( keepbackup )
717 {
718 QgsDebugMsgLevel( QStringLiteral( "Master password reset: backed up previous db at %1" ).arg( dbbackup ), 2 );
719 if ( backuppath )
720 *backuppath = dbbackup;
721 }
722
723 QgsDebugMsgLevel( QStringLiteral( "Master password reset: SUCCESS" ), 2 );
724 emit authDatabaseChanged();
725 return true;
726}
727
729{
731
732 mScheduledDbErase = scheduleErase;
733 // any call (start or stop) should reset these
734 mScheduledDbEraseRequestEmitted = false;
735 mScheduledDbEraseRequestCount = 0;
736
737 if ( scheduleErase )
738 {
739 if ( !mScheduledDbEraseTimer )
740 {
741 mScheduledDbEraseTimer = new QTimer( this );
742 connect( mScheduledDbEraseTimer, &QTimer::timeout, this, &QgsAuthManager::tryToStartDbErase );
743 mScheduledDbEraseTimer->start( mScheduledDbEraseRequestWait * 1000 );
744 }
745 else if ( !mScheduledDbEraseTimer->isActive() )
746 {
747 mScheduledDbEraseTimer->start();
748 }
749 }
750 else
751 {
752 if ( mScheduledDbEraseTimer && mScheduledDbEraseTimer->isActive() )
753 mScheduledDbEraseTimer->stop();
754 }
755}
756
758{
759 if ( isDisabled() )
760 return false;
761
762 qDeleteAll( mAuthMethods );
763 mAuthMethods.clear();
764 const QStringList methods = QgsAuthMethodRegistry::instance()->authMethodList();
765 for ( const auto &authMethodKey : methods )
766 {
767 mAuthMethods.insert( authMethodKey, QgsAuthMethodRegistry::instance()->createAuthMethod( authMethodKey ) );
768 }
769
770 return !mAuthMethods.isEmpty();
771}
772
774{
776
777 QStringList configids = configIds();
778 QString id;
779 int len = 7;
780
781 // Suppress warning: Potential leak of memory in qtimer.h [clang-analyzer-cplusplus.NewDeleteLeaks]
782#ifndef __clang_analyzer__
783 // sleep just a bit to make sure the current time has changed
784 QEventLoop loop;
785 QTimer::singleShot( 3, &loop, &QEventLoop::quit );
786 loop.exec();
787#endif
788
789 while ( true )
790 {
791 id.clear();
792 for ( int i = 0; i < len; i++ )
793 {
794 switch ( QRandomGenerator::system()->generate() % 2 )
795 {
796 case 0:
797 id += static_cast<char>( '0' + QRandomGenerator::system()->generate() % 10 );
798 break;
799 case 1:
800 id += static_cast<char>( 'a' + QRandomGenerator::system()->generate() % 26 );
801 break;
802 }
803 }
804 if ( !configids.contains( id ) )
805 {
806 break;
807 }
808 }
809 QgsDebugMsgLevel( QStringLiteral( "Generated unique ID: %1" ).arg( id ), 2 );
810 return id;
811}
812
813bool QgsAuthManager::configIdUnique( const QString &id ) const
814{
816
817 if ( isDisabled() )
818 return false;
819
820 if ( id.isEmpty() )
821 {
822 const char *err = QT_TR_NOOP( "Config ID is empty" );
823 QgsDebugError( err );
825 return false;
826 }
827 QStringList configids = configIds();
828 return !configids.contains( id );
829}
830
831bool QgsAuthManager::hasConfigId( const QString &txt )
832{
833 const thread_local QRegularExpression authCfgRegExp( AUTH_CFG_REGEX );
834 return txt.indexOf( authCfgRegExp ) != -1;
835}
836
838{
840
841 QMutexLocker locker( mMutex.get() );
842 QStringList providerAuthMethodsKeys;
843 if ( !dataprovider.isEmpty() )
844 {
845 providerAuthMethodsKeys = authMethodsKeys( dataprovider.toLower() );
846 }
847
848 QgsAuthMethodConfigsMap baseConfigs;
849
850 if ( isDisabled() )
851 return baseConfigs;
852
853 // Loop through all storages with capability ReadConfiguration and get the auth methods
855 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
856 {
857 QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
858 for ( const QgsAuthMethodConfig &config : std::as_const( configs ) )
859 {
860 if ( providerAuthMethodsKeys.isEmpty() || providerAuthMethodsKeys.contains( config.method() ) )
861 {
862 // Check if the config with that id is already in the list and warn if it is
863 if ( baseConfigs.contains( config.id() ) )
864 {
865 // This may not be an error, since the same config may be stored in multiple storages.
866 emit messageLog( tr( "A config with same id %1 was already added, skipping from %2" ).arg( config.id(), storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
867 }
868 else
869 {
870 baseConfigs.insert( config.id(), config );
871 }
872 }
873 }
874 }
875
876 if ( storages.empty() )
877 {
878 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
879 QgsDebugError( QStringLiteral( "No credentials storages found" ) );
880 }
881
882 return baseConfigs;
883
884}
885
887{
889
890 // Loop through all registered storages and get the auth methods
892 QStringList configIds;
893 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
894 {
895 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
896 for ( const QgsAuthMethodConfig &config : std::as_const( configs ) )
897 {
898 if ( ! configIds.contains( config.id() ) )
899 {
900 mConfigAuthMethods.insert( config.id(), config.method() );
901 QgsDebugMsgLevel( QStringLiteral( "Stored auth config/methods:\n%1 %2" ).arg( config.id(), config.method() ), 2 );
902 }
903 else
904 {
905 // This may not be an error, since the same config may be stored in multiple storages.
906 // A warning is issued when creating the list initially from availableAuthMethodConfigs()
907 QgsDebugMsgLevel( QStringLiteral( "A config with same id %1 was already added, skipping from %2" ).arg( config.id(), storage->name() ), 2 );
908 }
909 }
910 }
911}
912
914{
916
917 if ( isDisabled() )
918 return nullptr;
919
920 if ( !mConfigAuthMethods.contains( authcfg ) )
921 {
922 QgsDebugError( QStringLiteral( "No config auth method found in database for authcfg: %1" ).arg( authcfg ) );
923 return nullptr;
924 }
925
926 QString authMethodKey = mConfigAuthMethods.value( authcfg );
927
928 return authMethod( authMethodKey );
929}
930
931QString QgsAuthManager::configAuthMethodKey( const QString &authcfg ) const
932{
934
935 if ( isDisabled() )
936 return QString();
937
938 return mConfigAuthMethods.value( authcfg, QString() );
939}
940
941
942QStringList QgsAuthManager::authMethodsKeys( const QString &dataprovider )
943{
945
946 return authMethodsMap( dataprovider.toLower() ).keys();
947}
948
949QgsAuthMethod *QgsAuthManager::authMethod( const QString &authMethodKey )
950{
952
953 if ( !mAuthMethods.contains( authMethodKey ) )
954 {
955 QgsDebugError( QStringLiteral( "No auth method registered for auth method key: %1" ).arg( authMethodKey ) );
956 return nullptr;
957 }
958
959 return mAuthMethods.value( authMethodKey );
960}
961
962const QgsAuthMethodMetadata *QgsAuthManager::authMethodMetadata( const QString &authMethodKey )
963{
965
966 if ( !mAuthMethods.contains( authMethodKey ) )
967 {
968 QgsDebugError( QStringLiteral( "No auth method registered for auth method key: %1" ).arg( authMethodKey ) );
969 return nullptr;
970 }
971
972 return QgsAuthMethodRegistry::instance()->authMethodMetadata( authMethodKey );
973}
974
975
977{
979
980 if ( dataprovider.isEmpty() )
981 {
982 return mAuthMethods;
983 }
984
985 QgsAuthMethodsMap filteredmap;
986 QgsAuthMethodsMap::const_iterator i = mAuthMethods.constBegin();
987 while ( i != mAuthMethods.constEnd() )
988 {
989 if ( i.value()
990 && ( i.value()->supportedDataProviders().contains( QStringLiteral( "all" ) )
991 || i.value()->supportedDataProviders().contains( dataprovider ) ) )
992 {
993 filteredmap.insert( i.key(), i.value() );
994 }
995 ++i;
996 }
997 return filteredmap;
998}
999
1000#ifdef HAVE_GUI
1001QWidget *QgsAuthManager::authMethodEditWidget( const QString &authMethodKey, QWidget *parent )
1002{
1004
1005 QgsAuthMethod *method = authMethod( authMethodKey );
1006 if ( method )
1007 return method->editWidget( parent );
1008 else
1009 return nullptr;
1010}
1011#endif
1012
1014{
1016
1017 if ( isDisabled() )
1019
1020 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1021 if ( authmethod )
1022 {
1023 return authmethod->supportedExpansions();
1024 }
1026}
1027
1029{
1031
1032 QMutexLocker locker( mMutex.get() );
1033 if ( !setMasterPassword( true ) )
1034 return false;
1035
1036 // don't need to validate id, since it has not be defined yet
1037 if ( !config.isValid() )
1038 {
1039 const char *err = QT_TR_NOOP( "Store config: FAILED because config is invalid" );
1040 QgsDebugError( err );
1042 return false;
1043 }
1044
1045 QString uid = config.id();
1046 bool passedinID = !uid.isEmpty();
1047 if ( uid.isEmpty() )
1048 {
1049 uid = uniqueConfigId();
1050 }
1051 else if ( configIds().contains( uid ) )
1052 {
1053 if ( !overwrite )
1054 {
1055 const char *err = QT_TR_NOOP( "Store config: FAILED because pre-defined config ID %1 is not unique" );
1056 QgsDebugError( err );
1058 return false;
1059 }
1060 locker.unlock();
1061 if ( ! removeAuthenticationConfig( uid ) )
1062 {
1063 const char *err = QT_TR_NOOP( "Store config: FAILED because pre-defined config ID %1 could not be removed" );
1064 QgsDebugError( err );
1066 return false;
1067 }
1068 locker.relock();
1069 }
1070
1071 QString configstring = config.configString();
1072 if ( configstring.isEmpty() )
1073 {
1074 const char *err = QT_TR_NOOP( "Store config: FAILED because config string is empty" );
1075 QgsDebugError( err );
1077 return false;
1078 }
1079
1080 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateConfiguration ) )
1081 {
1082 if ( defaultStorage->isEncrypted() )
1083 {
1084 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
1085 }
1086
1087 // Make a copy to not alter the original config
1088 QgsAuthMethodConfig configCopy { config };
1089 configCopy.setId( uid );
1090 if ( !defaultStorage->storeMethodConfig( configCopy, configstring ) )
1091 {
1092 emit messageLog( tr( "Store config: FAILED to store config in default storage: %1" ).arg( defaultStorage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
1093 return false;
1094 }
1095 }
1096 else
1097 {
1098 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1099 return false;
1100 }
1101
1102 // passed-in config should now be like as if it was just loaded from db
1103 if ( !passedinID )
1104 config.setId( uid );
1105
1107
1108 QgsDebugMsgLevel( QStringLiteral( "Store config SUCCESS for authcfg: %1" ).arg( uid ), 2 );
1109 return true;
1110}
1111
1113{
1115
1116 QMutexLocker locker( mMutex.get() );
1117 if ( !setMasterPassword( true ) )
1118 return false;
1119
1120 // validate id
1121 if ( !config.isValid( true ) )
1122 {
1123 const char *err = QT_TR_NOOP( "Update config: FAILED because config is invalid" );
1124 QgsDebugError( err );
1126 return false;
1127 }
1128
1129 QString configstring = config.configString();
1130 if ( configstring.isEmpty() )
1131 {
1132 const char *err = QT_TR_NOOP( "Update config: FAILED because config is empty" );
1133 QgsDebugError( err );
1135 return false;
1136 }
1137
1138 // Loop through all storages with capability ReadConfiguration and update the first one that has the config
1140
1141 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1142 {
1143 if ( storage->methodConfigExists( config.id() ) )
1144 {
1146 {
1147 emit messageLog( tr( "Update config: FAILED because storage %1 does not support updating" ).arg( storage->name( ) ), authManTag(), Qgis::MessageLevel::Warning );
1148 return false;
1149 }
1150 if ( storage->isEncrypted() )
1151 {
1152 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
1153 }
1154 if ( !storage->storeMethodConfig( config, configstring ) )
1155 {
1156 emit messageLog( tr( "Store config: FAILED to store config in the storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1157 return false;
1158 }
1159 break;
1160 }
1161 }
1162
1163 if ( storages.empty() )
1164 {
1165 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1166 return false;
1167 }
1168
1169 // should come before updating auth methods, in case user switched auth methods in config
1170 clearCachedConfig( config.id() );
1171
1173
1174 QgsDebugMsgLevel( QStringLiteral( "Update config SUCCESS for authcfg: %1" ).arg( config.id() ), 2 );
1175
1176 return true;
1177}
1178
1179bool QgsAuthManager::loadAuthenticationConfig( const QString &authcfg, QgsAuthMethodConfig &config, bool full )
1180{
1182
1183 if ( isDisabled() )
1184 return false;
1185
1186 if ( full && !setMasterPassword( true ) )
1187 return false;
1188
1189 QMutexLocker locker( mMutex.get() );
1190
1191 // Loop through all storages with capability ReadConfiguration and get the config from the first one that has the config
1193
1194 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1195 {
1196 if ( storage->methodConfigExists( authcfg ) )
1197 {
1198 QString payload;
1199 config = storage->loadMethodConfig( authcfg, payload, full );
1200
1201 if ( ! config.isValid( true ) || ( full && payload.isEmpty() ) )
1202 {
1203 emit messageLog( tr( "Load config: FAILED to load config %1 from default storage: %2" ).arg( authcfg, storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1204 return false;
1205 }
1206
1207 if ( full )
1208 {
1209 if ( storage->isEncrypted() )
1210 {
1211 payload = QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), payload );
1212 }
1213 config.loadConfigString( payload );
1214 }
1215
1216 QString authMethodKey = configAuthMethodKey( authcfg );
1217 QgsAuthMethod *authmethod = authMethod( authMethodKey );
1218 if ( authmethod )
1219 {
1220 authmethod->updateMethodConfig( config );
1221 }
1222 else
1223 {
1224 QgsDebugError( QStringLiteral( "Update of authcfg %1 FAILED for auth method %2" ).arg( authcfg, authMethodKey ) );
1225 }
1226
1227 QgsDebugMsgLevel( QStringLiteral( "Load %1 config SUCCESS for authcfg: %2" ).arg( full ? "full" : "base", authcfg ), 2 );
1228 return true;
1229 }
1230 }
1231
1232 if ( storages.empty() )
1233 {
1234 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1235 }
1236 else
1237 {
1238 emit messageLog( tr( "Load config: FAILED to load config %1 from any storage" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
1239 }
1240
1241 return false;
1242}
1243
1245{
1247
1248 QMutexLocker locker( mMutex.get() );
1249 if ( isDisabled() )
1250 return false;
1251
1252 if ( authcfg.isEmpty() )
1253 return false;
1254
1255 // Loop through all storages with capability DeleteConfiguration and delete the first one that has the config
1257
1258 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1259 {
1260 if ( storage->methodConfigExists( authcfg ) )
1261 {
1262 if ( !storage->removeMethodConfig( authcfg ) )
1263 {
1264 emit messageLog( tr( "Remove config: FAILED to remove config from the storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Critical );
1265 return false;
1266 }
1267 else
1268 {
1269 clearCachedConfig( authcfg );
1271 QgsDebugMsgLevel( QStringLiteral( "REMOVED config for authcfg: %1" ).arg( authcfg ), 2 );
1272 return true;
1273 }
1274 break;
1275 }
1276 }
1277
1278 if ( storages.empty() )
1279 {
1280 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1281 }
1282 else
1283 {
1284 emit messageLog( tr( "Remove config: FAILED to remove config %1 from any storage" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
1285 }
1286
1287 return false;
1288
1289}
1290
1291bool QgsAuthManager::exportAuthenticationConfigsToXml( const QString &filename, const QStringList &authcfgs, const QString &password )
1292{
1294
1295 if ( filename.isEmpty() )
1296 return false;
1297
1298 QDomDocument document( QStringLiteral( "qgis_authentication" ) );
1299 QDomElement root = document.createElement( QStringLiteral( "qgis_authentication" ) );
1300 document.appendChild( root );
1301
1302 QString civ;
1303 if ( !password.isEmpty() )
1304 {
1305 QString salt;
1306 QString hash;
1307 QgsAuthCrypto::passwordKeyHash( password, &salt, &hash, &civ );
1308 root.setAttribute( QStringLiteral( "salt" ), salt );
1309 root.setAttribute( QStringLiteral( "hash" ), hash );
1310 root.setAttribute( QStringLiteral( "civ" ), civ );
1311 }
1312
1313 QDomElement configurations = document.createElement( QStringLiteral( "configurations" ) );
1314 for ( const QString &authcfg : authcfgs )
1315 {
1316 QgsAuthMethodConfig authMethodConfig;
1317
1318 bool ok = loadAuthenticationConfig( authcfg, authMethodConfig, true );
1319 if ( ok )
1320 {
1321 authMethodConfig.writeXml( configurations, document );
1322 }
1323 }
1324 if ( !password.isEmpty() )
1325 {
1326 QString configurationsString;
1327 QTextStream ts( &configurationsString );
1328#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1329 ts.setCodec( "UTF-8" );
1330#endif
1331 configurations.save( ts, 2 );
1332 root.appendChild( document.createTextNode( QgsAuthCrypto::encrypt( password, civ, configurationsString ) ) );
1333 }
1334 else
1335 {
1336 root.appendChild( configurations );
1337 }
1338
1339 QFile file( filename );
1340 if ( !file.open( QFile::WriteOnly | QIODevice::Truncate ) )
1341 return false;
1342
1343 QTextStream ts( &file );
1344#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1345 ts.setCodec( "UTF-8" );
1346#endif
1347 document.save( ts, 2 );
1348 file.close();
1349 return true;
1350}
1351
1352bool QgsAuthManager::importAuthenticationConfigsFromXml( const QString &filename, const QString &password, bool overwrite )
1353{
1355
1356 QFile file( filename );
1357 if ( !file.open( QFile::ReadOnly ) )
1358 {
1359 return false;
1360 }
1361
1362 QDomDocument document( QStringLiteral( "qgis_authentication" ) );
1363 if ( !document.setContent( &file ) )
1364 {
1365 file.close();
1366 return false;
1367 }
1368 file.close();
1369
1370 QDomElement root = document.documentElement();
1371 if ( root.tagName() != QLatin1String( "qgis_authentication" ) )
1372 {
1373 return false;
1374 }
1375
1376 QDomElement configurations;
1377 if ( root.hasAttribute( QStringLiteral( "salt" ) ) )
1378 {
1379 QString salt = root.attribute( QStringLiteral( "salt" ) );
1380 QString hash = root.attribute( QStringLiteral( "hash" ) );
1381 QString civ = root.attribute( QStringLiteral( "civ" ) );
1382 if ( !QgsAuthCrypto::verifyPasswordKeyHash( password, salt, hash ) )
1383 return false;
1384
1385 document.setContent( QgsAuthCrypto::decrypt( password, civ, root.text() ) );
1386 configurations = document.firstChild().toElement();
1387 }
1388 else
1389 {
1390 configurations = root.firstChildElement( QStringLiteral( "configurations" ) );
1391 }
1392
1393 QDomElement configuration = configurations.firstChildElement();
1394 while ( !configuration.isNull() )
1395 {
1396 QgsAuthMethodConfig authMethodConfig;
1397 authMethodConfig.readXml( configuration );
1398 storeAuthenticationConfig( authMethodConfig, overwrite );
1399
1400 configuration = configuration.nextSiblingElement();
1401 }
1402 return true;
1403}
1404
1406{
1408
1409 QMutexLocker locker( mMutex.get() );
1410 if ( isDisabled() )
1411 return false;
1412
1413 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::DeleteConfiguration ) )
1414 {
1415 if ( defaultStorage->clearMethodConfigs() )
1416 {
1419 QgsDebugMsgLevel( QStringLiteral( "REMOVED all configs from the default storage" ), 2 );
1420 return true;
1421 }
1422 else
1423 {
1424 QgsDebugMsgLevel( QStringLiteral( "FAILED to remove all configs from the default storage" ), 2 );
1425 return false;
1426 }
1427 }
1428 else
1429 {
1430 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1431 return false;
1432 }
1433}
1434
1435
1437{
1439
1440 QMutexLocker locker( mMutex.get() );
1441
1442 if ( sqliteDatabasePath().isEmpty() )
1443 {
1444 const char *err = QT_TR_NOOP( "The authentication database is not filesystem-based" );
1445 QgsDebugError( err );
1447 return false;
1448 }
1449
1450 if ( !QFile::exists( sqliteDatabasePath() ) )
1451 {
1452 const char *err = QT_TR_NOOP( "No authentication database found" );
1453 QgsDebugError( err );
1455 return false;
1456 }
1457
1458 // close any connection to current db
1460 QSqlDatabase authConn = authDatabaseConnection();
1462 if ( authConn.isValid() && authConn.isOpen() )
1463 authConn.close();
1464
1465 // duplicate current db file to 'qgis-auth_YYYY-MM-DD-HHMMSS.db' backup
1466 QString datestamp( QDateTime::currentDateTime().toString( QStringLiteral( "yyyy-MM-dd-hhmmss" ) ) );
1467 QString dbbackup( sqliteDatabasePath() );
1468 dbbackup.replace( QLatin1String( ".db" ), QStringLiteral( "_%1.db" ).arg( datestamp ) );
1469
1470 if ( !QFile::copy( sqliteDatabasePath(), dbbackup ) )
1471 {
1472 const char *err = QT_TR_NOOP( "Could not back up authentication database" );
1473 QgsDebugError( err );
1475 return false;
1476 }
1477
1478 if ( backuppath )
1479 *backuppath = dbbackup;
1480
1481 QgsDebugMsgLevel( QStringLiteral( "Backed up auth database at %1" ).arg( dbbackup ), 2 );
1482 return true;
1483}
1484
1485bool QgsAuthManager::eraseAuthenticationDatabase( bool backup, QString *backuppath )
1486{
1488
1489 QMutexLocker locker( mMutex.get() );
1490 if ( isDisabled() )
1491 return false;
1492
1493 QString dbbackup;
1494 if ( backup && !backupAuthenticationDatabase( &dbbackup ) )
1495 {
1496 emit messageLog( tr( "Failed to backup authentication database" ), authManTag(), Qgis::MessageLevel::Warning );
1497 return false;
1498 }
1499
1500 if ( backuppath && !dbbackup.isEmpty() )
1501 *backuppath = dbbackup;
1502
1503 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ClearStorage ) )
1504 {
1505 if ( defaultStorage->erase() )
1506 {
1507 mMasterPass = QString();
1510 QgsDebugMsgLevel( QStringLiteral( "ERASED all configs" ), 2 );
1511 return true;
1512 }
1513 else
1514 {
1515 QgsDebugMsgLevel( QStringLiteral( "FAILED to erase all configs" ), 2 );
1516 return false;
1517 }
1518 }
1519 else
1520 {
1521 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1522 return false;
1523 }
1524
1525#ifndef QT_NO_SSL
1526 initSslCaches();
1527#endif
1528
1529 emit authDatabaseChanged();
1530
1531 return true;
1532}
1533
1534bool QgsAuthManager::updateNetworkRequest( QNetworkRequest &request, const QString &authcfg,
1535 const QString &dataprovider )
1536{
1538
1539 if ( isDisabled() )
1540 return false;
1541
1542 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1543 if ( authmethod )
1544 {
1545 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkRequest ) )
1546 {
1547 QgsDebugError( QStringLiteral( "Network request updating not supported by authcfg: %1" ).arg( authcfg ) );
1548 return true;
1549 }
1550
1551 if ( !authmethod->updateNetworkRequest( request, authcfg, dataprovider.toLower() ) )
1552 {
1553 authmethod->clearCachedConfig( authcfg );
1554 return false;
1555 }
1556 return true;
1557 }
1558 return false;
1559}
1560
1561bool QgsAuthManager::updateNetworkReply( QNetworkReply *reply, const QString &authcfg,
1562 const QString &dataprovider )
1563{
1565
1566 if ( isDisabled() )
1567 return false;
1568
1569 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1570 if ( authmethod )
1571 {
1572 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkReply ) )
1573 {
1574 QgsDebugMsgLevel( QStringLiteral( "Network reply updating not supported by authcfg: %1" ).arg( authcfg ), 3 );
1575 return true;
1576 }
1577
1578 if ( !authmethod->updateNetworkReply( reply, authcfg, dataprovider.toLower() ) )
1579 {
1580 authmethod->clearCachedConfig( authcfg );
1581 return false;
1582 }
1583 return true;
1584 }
1585
1586 return false;
1587}
1588
1589bool QgsAuthManager::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
1590 const QString &dataprovider )
1591{
1593
1594 if ( isDisabled() )
1595 return false;
1596
1597 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1598 if ( authmethod )
1599 {
1600 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::DataSourceUri ) )
1601 {
1602 QgsDebugError( QStringLiteral( "Data source URI updating not supported by authcfg: %1" ).arg( authcfg ) );
1603 return true;
1604 }
1605
1606 if ( !authmethod->updateDataSourceUriItems( connectionItems, authcfg, dataprovider.toLower() ) )
1607 {
1608 authmethod->clearCachedConfig( authcfg );
1609 return false;
1610 }
1611 return true;
1612 }
1613
1614 return false;
1615}
1616
1617bool QgsAuthManager::updateNetworkProxy( QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider )
1618{
1620
1621 if ( isDisabled() )
1622 return false;
1623
1624 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1625 if ( authmethod )
1626 {
1627 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkProxy ) )
1628 {
1629 QgsDebugError( QStringLiteral( "Proxy updating not supported by authcfg: %1" ).arg( authcfg ) );
1630 return true;
1631 }
1632
1633 if ( !authmethod->updateNetworkProxy( proxy, authcfg, dataprovider.toLower() ) )
1634 {
1635 authmethod->clearCachedConfig( authcfg );
1636 return false;
1637 }
1638 QgsDebugMsgLevel( QStringLiteral( "Proxy updated successfully from authcfg: %1" ).arg( authcfg ), 2 );
1639 return true;
1640 }
1641
1642 return false;
1643}
1644
1645bool QgsAuthManager::storeAuthSetting( const QString &key, const QVariant &value, bool encrypt )
1646{
1648
1649 QMutexLocker locker( mMutex.get() );
1650 if ( key.isEmpty() )
1651 return false;
1652
1653 QString storeval( value.toString() );
1654 if ( encrypt )
1655 {
1656 if ( !setMasterPassword( true ) )
1657 {
1658 return false;
1659 }
1660 else
1661 {
1662 storeval = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), value.toString() );
1663 }
1664 }
1665
1666 if ( existsAuthSetting( key ) && ! removeAuthSetting( key ) )
1667 {
1668 emit messageLog( tr( "Store setting: FAILED to remove pre-existing setting %1" ).arg( key ), authManTag(), Qgis::MessageLevel::Warning );
1669 return false;
1670 }
1671
1672 // Set the setting in the first storage that has the capability to store it
1673
1674 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateSetting ) )
1675 {
1676 if ( !defaultStorage->storeAuthSetting( key, storeval ) )
1677 {
1678 emit messageLog( tr( "Store setting: FAILED to store setting in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
1679 return false;
1680 }
1681 return true;
1682 }
1683 else
1684 {
1685 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1686 return false;
1687 }
1688}
1689
1690QVariant QgsAuthManager::authSetting( const QString &key, const QVariant &defaultValue, bool decrypt )
1691{
1693
1694 QMutexLocker locker( mMutex.get() );
1695 if ( key.isEmpty() )
1696 return QVariant();
1697
1698 if ( decrypt && !setMasterPassword( true ) )
1699 return QVariant();
1700
1701 QVariant value = defaultValue;
1702
1703 // Loop through all storages with capability ReadSetting and get the setting from the first one that has the setting
1705
1706 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1707 {
1708 QString storeval = storage->loadAuthSetting( key );
1709 if ( !storeval.isEmpty() )
1710 {
1711 if ( decrypt )
1712 {
1713 storeval = QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), storeval );
1714 }
1715 value = storeval;
1716 break;
1717 }
1718 }
1719
1720 if ( storages.empty() )
1721 {
1722 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1723 }
1724
1725 return value;
1726}
1727
1728bool QgsAuthManager::existsAuthSetting( const QString &key )
1729{
1731
1732 QMutexLocker locker( mMutex.get() );
1733 if ( key.isEmpty() )
1734 return false;
1735
1736 // Loop through all storages with capability ReadSetting and get the setting from the first one that has the setting
1738
1739 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1740 {
1741
1742 if ( storage->authSettingExists( key ) )
1743 { return true; }
1744
1745 }
1746
1747 if ( storages.empty() )
1748 {
1749 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1750 }
1751
1752 return false;
1753}
1754
1755bool QgsAuthManager::removeAuthSetting( const QString &key )
1756{
1758
1759 QMutexLocker locker( mMutex.get() );
1760 if ( key.isEmpty() )
1761 return false;
1762
1763 // Loop through all storages with capability ReadSetting and delete from the first one that has the setting, fail if it has no capability
1765
1766 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1767 {
1768 if ( storage->authSettingExists( key ) )
1769 {
1771 {
1772 if ( !storage->removeAuthSetting( key ) )
1773 {
1774 emit messageLog( tr( "Remove setting: FAILED to remove setting from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
1775 return false;
1776 }
1777 return true;
1778 }
1779 else
1780 {
1781 emit messageLog( tr( "Remove setting: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
1782 return false;
1783 }
1784 }
1785 }
1786
1787 if ( storages.empty() )
1788 {
1789 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1790 }
1791 return false;
1792}
1793
1794#ifndef QT_NO_SSL
1795
1797
1799{
1800 QgsScopedRuntimeProfile profile( "Initialize SSL cache" );
1801
1802 QMutexLocker locker( mMutex.get() );
1803 bool res = true;
1804 res = res && rebuildCaCertsCache();
1805 res = res && rebuildCertTrustCache();
1806 res = res && rebuildTrustedCaCertsCache();
1807 res = res && rebuildIgnoredSslErrorCache();
1808 mCustomConfigByHostCache.clear();
1809 mHasCheckedIfCustomConfigByHostExists = false;
1810
1811 if ( !res )
1812 QgsDebugError( QStringLiteral( "Init of SSL caches FAILED" ) );
1813 return res;
1814}
1815
1816bool QgsAuthManager::storeCertIdentity( const QSslCertificate &cert, const QSslKey &key )
1817{
1819
1820 QMutexLocker locker( mMutex.get() );
1821 if ( cert.isNull() )
1822 {
1823 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
1824 return false;
1825 }
1826 if ( key.isNull() )
1827 {
1828 QgsDebugError( QStringLiteral( "Passed private key is null" ) );
1829 return false;
1830 }
1831
1832 if ( !setMasterPassword( true ) )
1833 return false;
1834
1835 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
1836
1837
1838 if ( existsCertIdentity( id ) && ! removeCertIdentity( id ) )
1839 {
1840 QgsDebugError( QStringLiteral( "Store certificate identity: FAILED to remove pre-existing certificate identity %1" ).arg( id ) );
1841 return false;
1842 }
1843
1844 QString keypem( QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), key.toPem() ) );
1845
1847 {
1848 if ( !defaultStorage->storeCertIdentity( cert, keypem ) )
1849 {
1850 emit messageLog( tr( "Store certificate identity: FAILED to store certificate identity in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
1851 return false;
1852 }
1853 return true;
1854 }
1855 else
1856 {
1857 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1858 return false;
1859 }
1860}
1861
1862const QSslCertificate QgsAuthManager::certIdentity( const QString &id )
1863{
1865
1866 QMutexLocker locker( mMutex.get() );
1867
1868 QSslCertificate cert;
1869
1870 if ( id.isEmpty() )
1871 return cert;
1872
1873 // Loop through all storages with capability ReadCertificateIdentity and get the certificate from the first one that has the certificate
1875
1876 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1877 {
1878 cert = storage->loadCertIdentity( id );
1879 if ( !cert.isNull() )
1880 {
1881 return cert;
1882 }
1883 }
1884
1885 if ( storages.empty() )
1886 {
1887 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1888 }
1889
1890 return cert;
1891}
1892
1893const QPair<QSslCertificate, QSslKey> QgsAuthManager::certIdentityBundle( const QString &id )
1894{
1896
1897 QMutexLocker locker( mMutex.get() );
1898 QPair<QSslCertificate, QSslKey> bundle;
1899 if ( id.isEmpty() )
1900 return bundle;
1901
1902 if ( !setMasterPassword( true ) )
1903 return bundle;
1904
1905 // Loop through all storages with capability ReadCertificateIdentity and get the certificate from the first one that has the certificate
1907
1908 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1909 {
1910 if ( storage->certIdentityExists( id ) )
1911 {
1912 QPair<QSslCertificate, QString> encryptedBundle { storage->loadCertIdentityBundle( id ) };
1913 if ( encryptedBundle.first.isNull() )
1914 {
1915 QgsDebugError( QStringLiteral( "Certificate identity bundle is null for id: %1" ).arg( id ) );
1916 return bundle;
1917 }
1918 QSslKey key( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), encryptedBundle.second ).toLatin1(),
1919 QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey );
1920 if ( key.isNull() )
1921 {
1922 QgsDebugError( QStringLiteral( "Certificate identity bundle: FAILED to create private key" ) );
1923 return bundle;
1924 }
1925 bundle = qMakePair( encryptedBundle.first, key );
1926 break;
1927 }
1928 }
1929
1930 if ( storages.empty() )
1931 {
1932 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
1933 return bundle;
1934 }
1935
1936 return bundle;
1937}
1938
1939const QStringList QgsAuthManager::certIdentityBundleToPem( const QString &id )
1940{
1942
1943 QMutexLocker locker( mMutex.get() );
1944 QPair<QSslCertificate, QSslKey> bundle( certIdentityBundle( id ) );
1945 if ( QgsAuthCertUtils::certIsViable( bundle.first ) && !bundle.second.isNull() )
1946 {
1947 return QStringList() << QString( bundle.first.toPem() ) << QString( bundle.second.toPem() );
1948 }
1949 return QStringList();
1950}
1951
1952const QList<QSslCertificate> QgsAuthManager::certIdentities()
1953{
1955
1956 QMutexLocker locker( mMutex.get() );
1957 QList<QSslCertificate> certs;
1958
1959 // Loop through all storages with capability ReadCertificateIdentity and collect the certificates from all storages
1961
1962 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
1963 {
1964 const QList<QSslCertificate> storageCerts = storage->certIdentities();
1965 // Add if not already in the list, warn otherwise
1966 for ( const QSslCertificate &cert : std::as_const( storageCerts ) )
1967 {
1968 if ( !certs.contains( cert ) )
1969 {
1970 certs.append( cert );
1971 }
1972 else
1973 {
1974 emit messageLog( tr( "Certificate already in the list: %1" ).arg( cert.issuerDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
1975 }
1976 }
1977 }
1978
1979 if ( storages.empty() )
1980 {
1981 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
1982 }
1983
1984 return certs;
1985}
1986
1988{
1990
1991 QMutexLocker locker( mMutex.get() );
1992
1993 if ( isDisabled() )
1994 return {};
1995
1996 // Loop through all storages with capability ReadCertificateIdentity and collect the certificate ids from all storages
1998
1999 QStringList ids;
2000
2001 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2002 {
2003 const QStringList storageIds = storage->certIdentityIds();
2004 // Add if not already in the list, warn otherwise
2005 for ( const QString &id : std::as_const( storageIds ) )
2006 {
2007 if ( !ids.contains( id ) )
2008 {
2009 ids.append( id );
2010 }
2011 else
2012 {
2013 emit messageLog( tr( "Certificate identity id already in the list: %1" ).arg( id ), authManTag(), Qgis::MessageLevel::Warning );
2014 }
2015 }
2016 }
2017
2018 return ids;
2019}
2020
2021bool QgsAuthManager::existsCertIdentity( const QString &id )
2022{
2024
2025 QMutexLocker locker( mMutex.get() );
2026 if ( id.isEmpty() )
2027 return false;
2028
2029 // Loop through all storages with capability ReadCertificateIdentity and check if the certificate exists in any storage
2031
2032 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2033 {
2034 if ( storage->certIdentityExists( id ) )
2035 {
2036 return true;
2037 }
2038 }
2039
2040 if ( storages.empty() )
2041 {
2042 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2043 }
2044
2045 return false;
2046}
2047
2048bool QgsAuthManager::removeCertIdentity( const QString &id )
2049{
2051
2052 QMutexLocker locker( mMutex.get() );
2053 if ( id.isEmpty() )
2054 {
2055 QgsDebugError( QStringLiteral( "Passed bundle ID is empty" ) );
2056 return false;
2057 }
2058
2059 // Loop through all storages with capability ReadCertificateIdentity and delete from the first one that has the bundle, fail if it has no capability
2061
2062 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2063 {
2064 if ( storage->certIdentityExists( id ) )
2065 {
2066 if ( !storage->removeCertIdentity( id ) )
2067 {
2068 emit messageLog( tr( "Remove certificate identity: FAILED to remove certificate identity from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2069 return false;
2070 }
2071 return true;
2072 }
2073 }
2074
2075 if ( storages.empty() )
2076 {
2077 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2078 }
2079
2080 return false;
2081
2082}
2083
2085{
2087
2088 QMutexLocker locker( mMutex.get() );
2089 if ( config.isNull() )
2090 {
2091 QgsDebugError( QStringLiteral( "Passed config is null" ) );
2092 return false;
2093 }
2094
2095 const QSslCertificate cert( config.sslCertificate() );
2096 const QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2097
2098 if ( existsSslCertCustomConfig( id, config.sslHostPort() ) && !removeSslCertCustomConfig( id, config.sslHostPort() ) )
2099 {
2100 QgsDebugError( QStringLiteral( "Store SSL certificate custom config: FAILED to remove pre-existing config %1" ).arg( id ) );
2101 return false;
2102 }
2103
2105 {
2106 if ( !defaultStorage->storeSslCertCustomConfig( config ) )
2107 {
2108 emit messageLog( tr( "Store SSL certificate custom config: FAILED to store config in default storage" ), authManTag(), Qgis::MessageLevel::Warning );
2109 return false;
2110 }
2111 }
2112 else
2113 {
2114 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2115 return false;
2116 }
2117
2119 mCustomConfigByHostCache.clear();
2120
2121 return true;
2122}
2123
2124const QgsAuthConfigSslServer QgsAuthManager::sslCertCustomConfig( const QString &id, const QString &hostport )
2125{
2127
2128 QMutexLocker locker( mMutex.get() );
2130
2131 if ( id.isEmpty() || hostport.isEmpty() )
2132 {
2133 QgsDebugError( QStringLiteral( "Passed config ID or host:port is empty" ) );
2134 return config;
2135 }
2136
2137 // Loop through all storages with capability ReadSslCertificateCustomConfig and get the config from the first one that has the config
2139
2140 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2141 {
2142 if ( storage->sslCertCustomConfigExists( id, hostport ) )
2143 {
2144 config = storage->loadSslCertCustomConfig( id, hostport );
2145 if ( !config.isNull() )
2146 {
2147 return config;
2148 }
2149 else
2150 {
2151 emit messageLog( tr( "Could not load SSL custom config %1 %2 from the storage." ).arg( id, hostport ), authManTag(), Qgis::MessageLevel::Critical );
2152 return config;
2153 }
2154 }
2155 }
2156
2157 if ( storages.empty() )
2158 {
2159 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2160 }
2161
2162 return config;
2163
2164}
2165
2167{
2169
2171 if ( hostport.isEmpty() )
2172 {
2173 return config;
2174 }
2175
2176 QMutexLocker locker( mMutex.get() );
2177
2178 if ( mCustomConfigByHostCache.contains( hostport ) )
2179 return mCustomConfigByHostCache.value( hostport );
2180
2181 // Loop through all storages with capability ReadSslCertificateCustomConfig and get the config from the first one that has the config
2183
2184 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2185 {
2186 config = storage->loadSslCertCustomConfigByHost( hostport );
2187 if ( !config.isNull() )
2188 {
2189 mCustomConfigByHostCache.insert( hostport, config );
2190 }
2191
2192 }
2193
2194 if ( storages.empty() )
2195 {
2196 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2197 }
2198
2199 return config;
2200}
2201
2202const QList<QgsAuthConfigSslServer> QgsAuthManager::sslCertCustomConfigs()
2203{
2205
2206 QMutexLocker locker( mMutex.get() );
2207 QList<QgsAuthConfigSslServer> configs;
2208
2209 // Loop through all storages with capability ReadSslCertificateCustomConfig
2211
2212 QStringList ids;
2213
2214 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2215 {
2216 const QList<QgsAuthConfigSslServer> storageConfigs = storage->sslCertCustomConfigs();
2217 // Check if id + hostPort is not already in the list, warn otherwise
2218 for ( const auto &config : std::as_const( storageConfigs ) )
2219 {
2220 const QString id( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ) );
2221 const QString hostPort = config.sslHostPort();
2222 const QString shaHostPort( QStringLiteral( "%1:%2" ).arg( id, hostPort ) );
2223 if ( ! ids.contains( shaHostPort ) )
2224 {
2225 ids.append( shaHostPort );
2226 configs.append( config );
2227 }
2228 else
2229 {
2230 emit messageLog( tr( "SSL custom config already in the list: %1" ).arg( hostPort ), authManTag(), Qgis::MessageLevel::Warning );
2231 }
2232 }
2233 configs.append( storageConfigs );
2234 }
2235
2236 if ( storages.empty() )
2237 {
2238 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2239 }
2240
2241 return configs;
2242}
2243
2244bool QgsAuthManager::existsSslCertCustomConfig( const QString &id, const QString &hostPort )
2245{
2247
2248 QMutexLocker locker( mMutex.get() );
2249 if ( id.isEmpty() || hostPort.isEmpty() )
2250 {
2251 QgsDebugError( QStringLiteral( "Passed config ID or host:port is empty" ) );
2252 return false;
2253 }
2254
2255 // Loop through all storages with capability ReadSslCertificateCustomConfig
2257
2258 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2259 {
2260 if ( storage->sslCertCustomConfigExists( id, hostPort ) )
2261 {
2262 return true;
2263 }
2264 }
2265
2266 if ( storages.empty() )
2267 {
2268 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2269 }
2270
2271 return false;
2272}
2273
2274bool QgsAuthManager::removeSslCertCustomConfig( const QString &id, const QString &hostport )
2275{
2277
2278 QMutexLocker locker( mMutex.get() );
2279 if ( id.isEmpty() || hostport.isEmpty() )
2280 {
2281 QgsDebugError( QStringLiteral( "Passed config ID or host:port is empty" ) );
2282 return false;
2283 }
2284
2285 mCustomConfigByHostCache.clear();
2286
2287 // Loop through all storages with capability DeleteSslCertificateCustomConfig
2289
2290 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2291 {
2292 if ( storage->sslCertCustomConfigExists( id, hostport ) )
2293 {
2294 if ( !storage->removeSslCertCustomConfig( id, hostport ) )
2295 {
2296 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 );
2297 return false;
2298 }
2299 const QString shaHostPort( QStringLiteral( "%1:%2" ).arg( id, hostport ) );
2300 if ( mIgnoredSslErrorsCache.contains( shaHostPort ) )
2301 {
2302 mIgnoredSslErrorsCache.remove( shaHostPort );
2303 }
2304 return true;
2305 }
2306 }
2307
2308 if ( storages.empty() )
2309 {
2310 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2311 }
2312
2313 return false;
2314}
2315
2316
2318{
2320
2321 QMutexLocker locker( mMutex.get() );
2322 if ( !mIgnoredSslErrorsCache.isEmpty() )
2323 {
2324 QgsDebugMsgLevel( QStringLiteral( "Ignored SSL errors cache items:" ), 1 );
2325 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = mIgnoredSslErrorsCache.constBegin();
2326 while ( i != mIgnoredSslErrorsCache.constEnd() )
2327 {
2328 QStringList errs;
2329 for ( auto err : i.value() )
2330 {
2332 }
2333 QgsDebugMsgLevel( QStringLiteral( "%1 = %2" ).arg( i.key(), errs.join( ", " ) ), 1 );
2334 ++i;
2335 }
2336 }
2337 else
2338 {
2339 QgsDebugMsgLevel( QStringLiteral( "Ignored SSL errors cache EMPTY" ), 2 );
2340 }
2341}
2342
2344{
2346
2347 QMutexLocker locker( mMutex.get() );
2348 if ( config.isNull() )
2349 {
2350 QgsDebugError( QStringLiteral( "Passed config is null" ) );
2351 return false;
2352 }
2353
2354 QString shahostport( QStringLiteral( "%1:%2" )
2355 .arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ).trimmed(),
2356 config.sslHostPort().trimmed() ) );
2357 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2358 {
2359 mIgnoredSslErrorsCache.remove( shahostport );
2360 }
2361 const QList<QSslError::SslError> errenums( config.sslIgnoredErrorEnums() );
2362 if ( !errenums.isEmpty() )
2363 {
2364 mIgnoredSslErrorsCache.insert( shahostport, QSet<QSslError::SslError>( errenums.begin(), errenums.end() ) );
2365 QgsDebugMsgLevel( QStringLiteral( "Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1" ).arg( shahostport ), 2 );
2367 return true;
2368 }
2369
2370 QgsDebugMsgLevel( QStringLiteral( "No ignored SSL errors to cache for sha:host:port = %1" ).arg( shahostport ), 2 );
2371 return true;
2372}
2373
2374bool QgsAuthManager::updateIgnoredSslErrorsCache( const QString &shahostport, const QList<QSslError> &errors )
2375{
2377
2378 QMutexLocker locker( mMutex.get() );
2379 const thread_local QRegularExpression rx( QRegularExpression::anchoredPattern( "\\S+:\\S+:\\d+" ) );
2380 if ( !rx.match( shahostport ).hasMatch() )
2381 {
2382 QgsDebugError( "Passed shahostport does not match \\S+:\\S+:\\d+, "
2383 "e.g. 74a4ef5ea94512a43769b744cda0ca5049a72491:www.example.com:443" );
2384 return false;
2385 }
2386
2387 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2388 {
2389 mIgnoredSslErrorsCache.remove( shahostport );
2390 }
2391
2392 if ( errors.isEmpty() )
2393 {
2394 QgsDebugError( QStringLiteral( "Passed errors list empty" ) );
2395 return false;
2396 }
2397
2398 QSet<QSslError::SslError> errs;
2399 for ( const auto &error : errors )
2400 {
2401 if ( error.error() == QSslError::NoError )
2402 continue;
2403
2404 errs.insert( error.error() );
2405 }
2406
2407 if ( errs.isEmpty() )
2408 {
2409 QgsDebugError( QStringLiteral( "Passed errors list does not contain errors" ) );
2410 return false;
2411 }
2412
2413 mIgnoredSslErrorsCache.insert( shahostport, errs );
2414
2415 QgsDebugMsgLevel( QStringLiteral( "Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1" ).arg( shahostport ), 2 );
2417 return true;
2418}
2419
2421{
2423
2424 QMutexLocker locker( mMutex.get() );
2425 QHash<QString, QSet<QSslError::SslError> > prevcache( mIgnoredSslErrorsCache );
2426 QHash<QString, QSet<QSslError::SslError> > nextcache;
2427
2428 // Loop through all storages with capability ReadSslCertificateCustomConfig
2430
2431 QStringList ids;
2432
2433 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2434 {
2435 const auto customConfigs { storage->sslCertCustomConfigs() };
2436 for ( const auto &config : std::as_const( customConfigs ) )
2437 {
2438 const QString shaHostPort( QStringLiteral( "%1:%2" ).arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ), config.sslHostPort() ) );
2439 if ( ! ids.contains( shaHostPort ) )
2440 {
2441 ids.append( shaHostPort );
2442 if ( !config.sslIgnoredErrorEnums().isEmpty() )
2443 {
2444 nextcache.insert( config.sslHostPort(), QSet<QSslError::SslError>( config.sslIgnoredErrorEnums().cbegin(), config.sslIgnoredErrorEnums().cend() ) );
2445 }
2446 if ( prevcache.contains( config.sslHostPort() ) )
2447 {
2448 prevcache.remove( config.sslHostPort() );
2449 }
2450 }
2451 else
2452 {
2453 emit messageLog( tr( "SSL custom config already in the list: %1" ).arg( config.sslHostPort() ), authManTag(), Qgis::MessageLevel::Warning );
2454 }
2455 }
2456 }
2457
2458 if ( !prevcache.isEmpty() )
2459 {
2460 // preserve any existing per-session ignored errors for hosts
2461 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = prevcache.constBegin();
2462 while ( i != prevcache.constEnd() )
2463 {
2464 nextcache.insert( i.key(), i.value() );
2465 ++i;
2466 }
2467 }
2468
2469 if ( nextcache != mIgnoredSslErrorsCache )
2470 {
2471 mIgnoredSslErrorsCache.clear();
2472 mIgnoredSslErrorsCache = nextcache;
2473 QgsDebugMsgLevel( QStringLiteral( "Rebuild of ignored SSL errors cache SUCCEEDED" ), 2 );
2475 return true;
2476 }
2477
2478 QgsDebugMsgLevel( QStringLiteral( "Rebuild of ignored SSL errors cache SAME AS BEFORE" ), 2 );
2480 return true;
2481}
2482
2483bool QgsAuthManager::storeCertAuthorities( const QList<QSslCertificate> &certs )
2484{
2486
2487 QMutexLocker locker( mMutex.get() );
2488 if ( certs.isEmpty() )
2489 {
2490 QgsDebugError( QStringLiteral( "Passed certificate list has no certs" ) );
2491 return false;
2492 }
2493
2494 for ( const auto &cert : certs )
2495 {
2496 if ( !storeCertAuthority( cert ) )
2497 return false;
2498 }
2499 return true;
2500}
2501
2502bool QgsAuthManager::storeCertAuthority( const QSslCertificate &cert )
2503{
2505
2506 QMutexLocker locker( mMutex.get() );
2507 // don't refuse !cert.isValid() (actually just expired) CAs,
2508 // as user may want to ignore that SSL connection error
2509 if ( cert.isNull() )
2510 {
2511 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2512 return false;
2513 }
2514
2515 if ( existsCertAuthority( cert ) && !removeCertAuthority( cert ) )
2516 {
2517 QgsDebugError( QStringLiteral( "Store certificate authority: FAILED to remove pre-existing certificate authority" ) );
2518 return false;
2519 }
2520
2522 {
2523 return defaultStorage->storeCertAuthority( cert );
2524 }
2525 else
2526 {
2527 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2528 return false;
2529 }
2530
2531 return false;
2532}
2533
2534const QSslCertificate QgsAuthManager::certAuthority( const QString &id )
2535{
2537
2538 QMutexLocker locker( mMutex.get() );
2539 QSslCertificate emptycert;
2540 QSslCertificate cert;
2541 if ( id.isEmpty() )
2542 return emptycert;
2543
2544 // Loop through all storages with capability ReadCertificateAuthority and get the certificate from the first one that has the certificate
2546
2547 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2548 {
2549 cert = storage->loadCertAuthority( id );
2550 if ( !cert.isNull() )
2551 {
2552 return cert;
2553 }
2554 }
2555
2556 if ( storages.empty() )
2557 {
2558 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2559 return emptycert;
2560 }
2561
2562 return cert;
2563}
2564
2565bool QgsAuthManager::existsCertAuthority( const QSslCertificate &cert )
2566{
2568
2569 QMutexLocker locker( mMutex.get() );
2570 if ( cert.isNull() )
2571 {
2572 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2573 return false;
2574 }
2575
2576 // Loop through all storages with capability ReadCertificateAuthority and get the certificate from the first one that has the certificate
2578
2579 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2580 {
2581 if ( storage->certAuthorityExists( cert ) )
2582 {
2583 return true;
2584 }
2585 }
2586
2587 if ( storages.empty() )
2588 {
2589 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2590 }
2591
2592 return false;
2593}
2594
2595bool QgsAuthManager::removeCertAuthority( const QSslCertificate &cert )
2596{
2598
2599 QMutexLocker locker( mMutex.get() );
2600 if ( cert.isNull() )
2601 {
2602 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2603 return false;
2604 }
2605
2606 // Loop through all storages with capability ReadCertificateAuthority and delete from the first one that has the certificate, fail if it has no capability
2608
2609 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2610 {
2611 if ( storage->certAuthorityExists( cert ) )
2612 {
2613
2615 {
2616 emit messageLog( tr( "Remove certificate: FAILED to remove setting from storage %1: storage is read only" ).arg( storage->name() ), authManTag(), Qgis::MessageLevel::Warning );
2617 return false;
2618 }
2619
2620 if ( !storage->removeCertAuthority( cert ) )
2621 {
2622 emit messageLog( tr( "Remove certificate authority: FAILED to remove certificate authority from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2623 return false;
2624 }
2625 return true;
2626 }
2627 }
2628
2629 if ( storages.empty() )
2630 {
2631 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2632 }
2633
2634 return false;
2635}
2636
2637const QList<QSslCertificate> QgsAuthManager::systemRootCAs()
2638{
2639 return QSslConfiguration::systemCaCertificates();
2640}
2641
2642const QList<QSslCertificate> QgsAuthManager::extraFileCAs()
2643{
2645
2646 QMutexLocker locker( mMutex.get() );
2647 QList<QSslCertificate> certs;
2648 QList<QSslCertificate> filecerts;
2649 QVariant cafileval = QgsAuthManager::instance()->authSetting( QStringLiteral( "cafile" ) );
2650 if ( QgsVariantUtils::isNull( cafileval ) )
2651 return certs;
2652
2653 QVariant allowinvalid = QgsAuthManager::instance()->authSetting( QStringLiteral( "cafileallowinvalid" ), QVariant( false ) );
2654 if ( QgsVariantUtils::isNull( allowinvalid ) )
2655 return certs;
2656
2657 QString cafile( cafileval.toString() );
2658 if ( !cafile.isEmpty() && QFile::exists( cafile ) )
2659 {
2660 filecerts = QgsAuthCertUtils::certsFromFile( cafile );
2661 }
2662 // only CAs or certs capable of signing other certs are allowed
2663 for ( const auto &cert : std::as_const( filecerts ) )
2664 {
2665 if ( !allowinvalid.toBool() && ( cert.isBlacklisted()
2666 || cert.isNull()
2667 || cert.expiryDate() <= QDateTime::currentDateTime()
2668 || cert.effectiveDate() > QDateTime::currentDateTime() ) )
2669 {
2670 continue;
2671 }
2672
2674 {
2675 certs << cert;
2676 }
2677 }
2678 return certs;
2679}
2680
2681const QList<QSslCertificate> QgsAuthManager::databaseCAs()
2682{
2684
2685 QMutexLocker locker( mMutex.get() );
2686
2687 // Loop through all storages with capability ReadCertificateAuthority and collect the certificates from all storages
2689
2690 QList<QSslCertificate> certs;
2691
2692 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2693 {
2694 const QList<QSslCertificate> storageCerts = storage->caCerts();
2695 // Add if not already in the list, warn otherwise
2696 for ( const QSslCertificate &cert : std::as_const( storageCerts ) )
2697 {
2698 if ( !certs.contains( cert ) )
2699 {
2700 certs.append( cert );
2701 }
2702 else
2703 {
2704 emit messageLog( tr( "Certificate already in the list: %1" ).arg( cert.issuerDisplayName() ), authManTag(), Qgis::MessageLevel::Warning );
2705 }
2706 }
2707 }
2708
2709 if ( storages.empty() )
2710 {
2711 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2712 }
2713
2714 return certs;
2715}
2716
2717const QMap<QString, QSslCertificate> QgsAuthManager::mappedDatabaseCAs()
2718{
2720
2721 QMutexLocker locker( mMutex.get() );
2723}
2724
2726{
2728
2729 QMutexLocker locker( mMutex.get() );
2730 mCaCertsCache.clear();
2731 // in reverse order of precedence, with regards to duplicates, so QMap inserts overwrite
2732 insertCaCertInCache( QgsAuthCertUtils::SystemRoot, systemRootCAs() );
2733 insertCaCertInCache( QgsAuthCertUtils::FromFile, extraFileCAs() );
2734 insertCaCertInCache( QgsAuthCertUtils::InDatabase, databaseCAs() );
2735
2736 bool res = !mCaCertsCache.isEmpty(); // should at least contain system root CAs
2737 if ( !res )
2738 QgsDebugError( QStringLiteral( "Rebuild of CA certs cache FAILED" ) );
2739 return res;
2740}
2741
2743{
2745
2746 QMutexLocker locker( mMutex.get() );
2747 if ( cert.isNull() )
2748 {
2749 QgsDebugError( QStringLiteral( "Passed certificate is null." ) );
2750 return false;
2751 }
2752
2753 if ( certTrustPolicy( cert ) == policy )
2754 {
2755 return true;
2756 }
2757
2759 {
2760 emit messageLog( tr( "Could not delete pre-existing certificate trust policy." ), authManTag(), Qgis::MessageLevel::Warning );
2761 return false;
2762 }
2763
2765 {
2766 return defaultStorage->storeCertTrustPolicy( cert, policy );
2767 }
2768 else
2769 {
2770 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
2771 return false;
2772 }
2773}
2774
2776{
2778
2779 QMutexLocker locker( mMutex.get() );
2780 if ( cert.isNull() )
2781 {
2782 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2784 }
2785
2786 // Loop through all storages with capability ReadCertificateTrustPolicy and get the policy from the first one that has the policy
2788
2789 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2790 {
2792 if ( policy != QgsAuthCertUtils::DefaultTrust )
2793 {
2794 return policy;
2795 }
2796 }
2797
2798 if ( storages.empty() )
2799 {
2800 emit messageLog( tr( "Could not connect to any credentials storage." ), authManTag(), Qgis::MessageLevel::Critical );
2801 }
2802
2804}
2805
2806bool QgsAuthManager::removeCertTrustPolicies( const QList<QSslCertificate> &certs )
2807{
2809
2810 QMutexLocker locker( mMutex.get() );
2811 if ( certs.empty() )
2812 {
2813 QgsDebugError( QStringLiteral( "Passed certificate list has no certs" ) );
2814 return false;
2815 }
2816
2817 for ( const auto &cert : certs )
2818 {
2819 if ( !removeCertTrustPolicy( cert ) )
2820 return false;
2821 }
2822 return true;
2823}
2824
2825bool QgsAuthManager::removeCertTrustPolicy( const QSslCertificate &cert )
2826{
2828
2829 QMutexLocker locker( mMutex.get() );
2830 if ( cert.isNull() )
2831 {
2832 QgsDebugError( QStringLiteral( "Passed certificate is null" ) );
2833 return false;
2834 }
2835
2836 // Loop through all storages with capability ReadCertificateTrustPolicy and delete from the first one that has the policy, fail if it has no capability
2838
2839 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2840 {
2841 if ( storage->certTrustPolicyExists( cert ) )
2842 {
2844 {
2845 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 );
2846 return false;
2847 }
2848
2849 if ( !storage->removeCertTrustPolicy( cert ) )
2850 {
2851 emit messageLog( tr( "Remove certificate trust policy: FAILED to remove certificate trust policy from storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
2852 return false;
2853 }
2854 return true;
2855 }
2856 }
2857
2858 if ( storages.empty() )
2859 {
2860 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
2861 }
2862
2863 return false;
2864}
2865
2867{
2869
2870 QMutexLocker locker( mMutex.get() );
2871 if ( cert.isNull() )
2872 {
2874 }
2875
2876 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2877 const QStringList &trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
2878 const QStringList &untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
2879
2881 if ( trustedids.contains( id ) )
2882 {
2884 }
2885 else if ( untrustedids.contains( id ) )
2886 {
2888 }
2889 return policy;
2890}
2891
2893{
2895
2896 if ( policy == QgsAuthCertUtils::DefaultTrust )
2897 {
2898 // set default trust policy to Trusted by removing setting
2899 return removeAuthSetting( QStringLiteral( "certdefaulttrust" ) );
2900 }
2901 return storeAuthSetting( QStringLiteral( "certdefaulttrust" ), static_cast< int >( policy ) );
2902}
2903
2905{
2907
2908 QMutexLocker locker( mMutex.get() );
2909 QVariant policy( authSetting( QStringLiteral( "certdefaulttrust" ) ) );
2910 if ( QgsVariantUtils::isNull( policy ) )
2911 {
2913 }
2914 return static_cast< QgsAuthCertUtils::CertTrustPolicy >( policy.toInt() );
2915}
2916
2918{
2920
2921 QMutexLocker locker( mMutex.get() );
2922 mCertTrustCache.clear();
2923
2924 // Loop through all storages with capability ReadCertificateTrustPolicy
2926
2927 QStringList ids;
2928
2929 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
2930 {
2931
2932 const auto trustedCerts { storage->caCertsPolicy() };
2933 for ( auto it = trustedCerts.cbegin(); it != trustedCerts.cend(); ++it )
2934 {
2935 const QString id { it.key( )};
2936 if ( ! ids.contains( id ) )
2937 {
2938 ids.append( id );
2939 const QgsAuthCertUtils::CertTrustPolicy policy( it.value() );
2941 {
2942 QStringList ids;
2943 if ( mCertTrustCache.contains( QgsAuthCertUtils::Trusted ) )
2944 {
2945 ids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
2946 }
2947 mCertTrustCache.insert( QgsAuthCertUtils::Trusted, ids << it.key() );
2948 }
2949 }
2950 else
2951 {
2952 emit messageLog( tr( "Certificate already in the list: %1" ).arg( it.key() ), authManTag(), Qgis::MessageLevel::Warning );
2953 }
2954 }
2955 }
2956
2957 if ( ! storages.empty() )
2958 {
2959 QgsDebugMsgLevel( QStringLiteral( "Rebuild of cert trust policy cache SUCCEEDED" ), 2 );
2960 return true;
2961 }
2962 else
2963 {
2964 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
2965 return false;
2966 }
2967}
2968
2969const QList<QSslCertificate> QgsAuthManager::trustedCaCerts( bool includeinvalid )
2970{
2972
2973 QMutexLocker locker( mMutex.get() );
2975 QStringList trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
2976 QStringList untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
2977 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
2978
2979 QList<QSslCertificate> trustedcerts;
2980 for ( int i = 0; i < certpairs.size(); ++i )
2981 {
2982 QSslCertificate cert( certpairs.at( i ).second );
2983 QString certid( QgsAuthCertUtils::shaHexForCert( cert ) );
2984 if ( trustedids.contains( certid ) )
2985 {
2986 // trusted certs are always added regardless of their validity
2987 trustedcerts.append( cert );
2988 }
2989 else if ( defaultpolicy == QgsAuthCertUtils::Trusted && !untrustedids.contains( certid ) )
2990 {
2991 if ( !includeinvalid && !QgsAuthCertUtils::certIsViable( cert ) )
2992 continue;
2993 trustedcerts.append( cert );
2994 }
2995 }
2996
2997 // update application default SSL config for new requests
2998 QSslConfiguration sslconfig( QSslConfiguration::defaultConfiguration() );
2999 sslconfig.setCaCertificates( trustedcerts );
3000 QSslConfiguration::setDefaultConfiguration( sslconfig );
3001
3002 return trustedcerts;
3003}
3004
3005const QList<QSslCertificate> QgsAuthManager::untrustedCaCerts( QList<QSslCertificate> trustedCAs )
3006{
3008
3009 QMutexLocker locker( mMutex.get() );
3010 if ( trustedCAs.isEmpty() )
3011 {
3012 if ( mTrustedCaCertsCache.isEmpty() )
3013 {
3015 }
3016 trustedCAs = trustedCaCertsCache();
3017 }
3018
3019 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
3020
3021 QList<QSslCertificate> untrustedCAs;
3022 for ( int i = 0; i < certpairs.size(); ++i )
3023 {
3024 QSslCertificate cert( certpairs.at( i ).second );
3025 if ( !trustedCAs.contains( cert ) )
3026 {
3027 untrustedCAs.append( cert );
3028 }
3029 }
3030 return untrustedCAs;
3031}
3032
3034{
3036
3037 QMutexLocker locker( mMutex.get() );
3038 mTrustedCaCertsCache = trustedCaCerts();
3039 QgsDebugMsgLevel( QStringLiteral( "Rebuilt trusted cert authorities cache" ), 2 );
3040 // TODO: add some error trapping for the operation
3041 return true;
3042}
3043
3045{
3047
3048 QMutexLocker locker( mMutex.get() );
3050}
3051
3053{
3055
3056 QMutexLocker locker( mMutex.get() );
3057 if ( masterPasswordIsSet() )
3058 {
3059 return passwordHelperWrite( mMasterPass );
3060 }
3061 return false;
3062}
3063
3064
3066
3067#endif
3068
3070{
3072
3073 if ( isDisabled() )
3074 return;
3075
3076 const QStringList ids = configIds();
3077 for ( const auto &authcfg : ids )
3078 {
3079 clearCachedConfig( authcfg );
3080 }
3081}
3082
3083void QgsAuthManager::clearCachedConfig( const QString &authcfg )
3084{
3086
3087 if ( isDisabled() )
3088 return;
3089
3090 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
3091 if ( authmethod )
3092 {
3093 authmethod->clearCachedConfig( authcfg );
3094 }
3095}
3096
3097void QgsAuthManager::writeToConsole( const QString &message,
3098 const QString &tag,
3099 Qgis::MessageLevel level )
3100{
3101 Q_UNUSED( tag )
3102
3104
3105 // only output WARNING and CRITICAL messages
3106 if ( level == Qgis::MessageLevel::Info )
3107 return;
3108
3109 QString msg;
3110 switch ( level )
3111 {
3113 msg += QLatin1String( "WARNING: " );
3114 break;
3116 msg += QLatin1String( "ERROR: " );
3117 break;
3118 default:
3119 break;
3120 }
3121 msg += message;
3122
3123 QTextStream out( stdout, QIODevice::WriteOnly );
3124 out << msg << Qt::endl;
3125}
3126
3127void QgsAuthManager::tryToStartDbErase()
3128{
3130
3131 ++mScheduledDbEraseRequestCount;
3132 // wait a total of 90 seconds for GUI availiability or user interaction, then cancel schedule
3133 int trycutoff = 90 / ( mScheduledDbEraseRequestWait ? mScheduledDbEraseRequestWait : 3 );
3134 if ( mScheduledDbEraseRequestCount >= trycutoff )
3135 {
3137 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emitting/scheduling canceled" ), 2 );
3138 return;
3139 }
3140 else
3141 {
3142 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest attempt (%1 of %2)" )
3143 .arg( mScheduledDbEraseRequestCount ).arg( trycutoff ), 2 );
3144 }
3145
3146 if ( scheduledAuthDatabaseErase() && !mScheduledDbEraseRequestEmitted && mMutex->tryLock() )
3147 {
3148 // see note in header about this signal's use
3149 mScheduledDbEraseRequestEmitted = true;
3151
3152 mMutex->unlock();
3153
3154 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emitted" ), 2 );
3155 return;
3156 }
3157 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emit skipped" ), 2 );
3158}
3159
3160
3162{
3163 QMutexLocker locker( mMutex.get() );
3164
3165 QMapIterator<QThread *, QMetaObject::Connection> iterator( mConnectedThreads );
3166 while ( iterator.hasNext() )
3167 {
3168 iterator.next();
3169 QThread::disconnect( iterator.value() );
3170 }
3171
3172 if ( !mAuthInit )
3173 return;
3174
3175 locker.unlock();
3176
3177 if ( !isDisabled() )
3178 {
3180 qDeleteAll( mAuthMethods );
3181
3183 QSqlDatabase authConn = authDatabaseConnection();
3185 if ( authConn.isValid() && authConn.isOpen() )
3186 authConn.close();
3187 }
3188 delete mScheduledDbEraseTimer;
3189 mScheduledDbEraseTimer = nullptr;
3190 QSqlDatabase::removeDatabase( QStringLiteral( "authentication.configs" ) );
3191}
3192
3194{
3195 QMutexLocker locker( mMutex.get() );
3196 if ( ! mAuthConfigurationStorageRegistry )
3197 {
3198 mAuthConfigurationStorageRegistry = std::make_unique<QgsAuthConfigurationStorageRegistry>();
3199 }
3200 return mAuthConfigurationStorageRegistry.get();
3201}
3202
3203
3204QString QgsAuthManager::passwordHelperName() const
3205{
3206 return tr( "Password Helper" );
3207}
3208
3209
3210void QgsAuthManager::passwordHelperLog( const QString &msg ) const
3211{
3213
3215 {
3216 QgsMessageLog::logMessage( msg, passwordHelperName() );
3217 }
3218}
3219
3221{
3223
3224 passwordHelperLog( tr( "Opening %1 for DELETE…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3225 bool result;
3226 QKeychain::DeletePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3227 QgsSettings settings;
3228 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3229 job.setAutoDelete( false );
3230 job.setKey( authPasswordHelperKeyName() );
3231 QEventLoop loop;
3232 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3233 job.start();
3234 loop.exec();
3235 if ( job.error() )
3236 {
3237 mPasswordHelperErrorCode = job.error();
3238 mPasswordHelperErrorMessage = tr( "Delete password failed: %1." ).arg( job.errorString() );
3239 // Signals used in the tests to exit main application loop
3240 emit passwordHelperFailure();
3241 result = false;
3242 }
3243 else
3244 {
3245 // Signals used in the tests to exit main application loop
3246 emit passwordHelperSuccess();
3247 result = true;
3248 }
3249 passwordHelperProcessError();
3250 return result;
3251}
3252
3253QString QgsAuthManager::passwordHelperRead()
3254{
3256
3257 // Retrieve it!
3258 QString password;
3259 passwordHelperLog( tr( "Opening %1 for READ…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3260 QKeychain::ReadPasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3261 QgsSettings settings;
3262 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3263 job.setAutoDelete( false );
3264 job.setKey( authPasswordHelperKeyName() );
3265 QEventLoop loop;
3266 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3267 job.start();
3268 loop.exec();
3269 if ( job.error() )
3270 {
3271 mPasswordHelperErrorCode = job.error();
3272 mPasswordHelperErrorMessage = tr( "Retrieving password from your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() );
3273 // Signals used in the tests to exit main application loop
3274 emit passwordHelperFailure();
3275 }
3276 else
3277 {
3278 password = job.textData();
3279 // Password is there but it is empty, treat it like if it was not found
3280 if ( password.isEmpty() )
3281 {
3282 mPasswordHelperErrorCode = QKeychain::EntryNotFound;
3283 mPasswordHelperErrorMessage = tr( "Empty password retrieved from your %1." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME );
3284 // Signals used in the tests to exit main application loop
3285 emit passwordHelperFailure();
3286 }
3287 else
3288 {
3289 // Signals used in the tests to exit main application loop
3290 emit passwordHelperSuccess();
3291 }
3292 }
3293 passwordHelperProcessError();
3294 return password;
3295}
3296
3297bool QgsAuthManager::passwordHelperWrite( const QString &password )
3298{
3300
3301 Q_ASSERT( !password.isEmpty() );
3302 bool result;
3303 passwordHelperLog( tr( "Opening %1 for WRITE…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3304 QKeychain::WritePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3305 QgsSettings settings;
3306 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3307 job.setAutoDelete( false );
3308 job.setKey( authPasswordHelperKeyName() );
3309 job.setTextData( password );
3310 QEventLoop loop;
3311 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3312 job.start();
3313 loop.exec();
3314 if ( job.error() )
3315 {
3316 mPasswordHelperErrorCode = job.error();
3317 mPasswordHelperErrorMessage = tr( "Storing password in your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() );
3318 // Signals used in the tests to exit main application loop
3319 emit passwordHelperFailure();
3320 result = false;
3321 }
3322 else
3323 {
3324 passwordHelperClearErrors();
3325 // Signals used in the tests to exit main application loop
3326 emit passwordHelperSuccess();
3327 result = true;
3328 }
3329 passwordHelperProcessError();
3330 return result;
3331}
3332
3334{
3335 // Does the user want to store the password in the wallet?
3336 QgsSettings settings;
3337 return settings.value( QStringLiteral( "use_password_helper" ), true, QgsSettings::Section::Auth ).toBool();
3338}
3339
3341{
3342 QgsSettings settings;
3343 settings.setValue( QStringLiteral( "use_password_helper" ), enabled, QgsSettings::Section::Auth );
3344 emit messageLog( enabled ? tr( "Your %1 will be <b>used from now</b> on to store and retrieve the master password." )
3346 tr( "Your %1 will <b>not be used anymore</b> to store and retrieve the master password." )
3348}
3349
3351{
3352 // Does the user want to store the password in the wallet?
3353 QgsSettings settings;
3354 return settings.value( QStringLiteral( "password_helper_logging" ), false, QgsSettings::Section::Auth ).toBool();
3355}
3356
3358{
3359 QgsSettings settings;
3360 settings.setValue( QStringLiteral( "password_helper_logging" ), enabled, QgsSettings::Section::Auth );
3361}
3362
3363void QgsAuthManager::passwordHelperClearErrors()
3364{
3365 mPasswordHelperErrorCode = QKeychain::NoError;
3366 mPasswordHelperErrorMessage.clear();
3367}
3368
3369void QgsAuthManager::passwordHelperProcessError()
3370{
3372
3373 if ( mPasswordHelperErrorCode == QKeychain::AccessDenied ||
3374 mPasswordHelperErrorCode == QKeychain::AccessDeniedByUser ||
3375 mPasswordHelperErrorCode == QKeychain::NoBackendAvailable ||
3376 mPasswordHelperErrorCode == QKeychain::NotImplemented )
3377 {
3378 // If the error is permanent or the user denied access to the wallet
3379 // we also want to disable the wallet system to prevent annoying
3380 // notification on each subsequent access.
3381 setPasswordHelperEnabled( false );
3382 mPasswordHelperErrorMessage = tr( "There was an error and integration with your %1 system has been disabled. "
3383 "You can re-enable it at any time through the \"Utilities\" menu "
3384 "in the Authentication pane of the options dialog. %2" )
3385 .arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage );
3386 }
3387 if ( mPasswordHelperErrorCode != QKeychain::NoError )
3388 {
3389 // We've got an error from the wallet
3390 passwordHelperLog( tr( "Error in %1: %2" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage ) );
3391 emit passwordHelperMessageLog( mPasswordHelperErrorMessage, authManTag(), Qgis::MessageLevel::Critical );
3392 }
3393 passwordHelperClearErrors();
3394}
3395
3396
3397bool QgsAuthManager::masterPasswordInput()
3398{
3400
3401 if ( isDisabled() )
3402 return false;
3403
3404 QString pass;
3405 bool storedPasswordIsValid = false;
3406 bool ok = false;
3407
3408 // Read the password from the wallet
3409 if ( passwordHelperEnabled() )
3410 {
3411 pass = passwordHelperRead();
3412 if ( ! pass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) )
3413 {
3414 // Let's check the password!
3415 if ( verifyMasterPassword( pass ) )
3416 {
3417 ok = true;
3418 storedPasswordIsValid = true;
3419 emit passwordHelperMessageLog( tr( "Master password has been successfully read from your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), Qgis::MessageLevel::Info );
3420 }
3421 else
3422 {
3423 emit passwordHelperMessageLog( tr( "Master password stored in your %1 is not valid" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), Qgis::MessageLevel::Warning );
3424 }
3425 }
3426 }
3427
3428 if ( ! ok )
3429 {
3430 pass.clear();
3432 }
3433
3434 if ( ok && !pass.isEmpty() && mMasterPass != pass )
3435 {
3436 mMasterPass = pass;
3437 if ( passwordHelperEnabled() && ! storedPasswordIsValid )
3438 {
3439 if ( passwordHelperWrite( pass ) )
3440 {
3441 emit passwordHelperMessageLog( tr( "Master password has been successfully written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), Qgis::MessageLevel::Info );
3442 }
3443 else
3444 {
3445 emit passwordHelperMessageLog( tr( "Master password could not be written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), Qgis::MessageLevel::Warning );
3446 }
3447 }
3448 return true;
3449 }
3450 return false;
3451}
3452
3453bool QgsAuthManager::masterPasswordRowsInDb( int *rows ) const
3454{
3456
3457 if ( isDisabled() )
3458 return false;
3459
3460 *rows = 0;
3461
3462 QMutexLocker locker( mMutex.get() );
3463
3464 // Loop through all storages with capability ReadMasterPassword and count the number of master passwords
3466
3467 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3468 {
3469 try
3470 {
3471 *rows += storage->masterPasswords( ).count();
3472 }
3473 catch ( const QgsNotSupportedException &e )
3474 {
3475 // It should not happen because we are checking the capability in advance
3477 }
3478 }
3479
3480 if ( storages.empty() )
3481 {
3482 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3483 }
3484
3485 return rows != 0;
3486
3487}
3488
3490{
3492
3493 if ( isDisabled() )
3494 return false;
3495
3496 int rows = 0;
3497 if ( !masterPasswordRowsInDb( &rows ) )
3498 {
3499 const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
3500 QgsDebugError( err );
3502
3503 return false;
3504 }
3505 return ( rows == 1 );
3506}
3507
3508bool QgsAuthManager::masterPasswordCheckAgainstDb( const QString &compare ) const
3509{
3511
3512 if ( isDisabled() )
3513 return false;
3514
3515 // Only check the default DB
3516 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ReadMasterPassword ) )
3517 {
3518 try
3519 {
3520 const QList<QgsAuthConfigurationStorage::MasterPasswordConfig> passwords { defaultStorage->masterPasswords( ) };
3521 if ( passwords.size() == 0 )
3522 {
3523 emit messageLog( tr( "Master password: FAILED to access database" ), authManTag(), Qgis::MessageLevel::Critical );
3524 return false;
3525 }
3526 const QgsAuthConfigurationStorage::MasterPasswordConfig storedPassword { passwords.first() };
3527 return QgsAuthCrypto::verifyPasswordKeyHash( compare.isNull() ? mMasterPass : compare, storedPassword.salt, storedPassword.hash );
3528 }
3529 catch ( const QgsNotSupportedException &e )
3530 {
3531 // It should not happen because we are checking the capability in advance
3533 return false;
3534 }
3535
3536 }
3537 else
3538 {
3539 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3540 return false;
3541 }
3542}
3543
3544bool QgsAuthManager::masterPasswordStoreInDb() const
3545{
3547
3548 if ( isDisabled() )
3549 return false;
3550
3551 QString salt, hash, civ;
3552 QgsAuthCrypto::passwordKeyHash( mMasterPass, &salt, &hash, &civ );
3553
3554 // Only store in the default DB
3555 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::CreateMasterPassword ) )
3556 {
3557 try
3558 {
3559 return defaultStorage->storeMasterPassword( { salt, civ, hash } );
3560 }
3561 catch ( const QgsNotSupportedException &e )
3562 {
3563 // It should not happen because we are checking the capability in advance
3565 return false;
3566 }
3567 }
3568 else
3569 {
3570 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3571 return false;
3572 }
3573}
3574
3575bool QgsAuthManager::masterPasswordClearDb()
3576{
3578
3579 if ( isDisabled() )
3580 return false;
3581
3582 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::DeleteMasterPassword ) )
3583 {
3584
3585 try
3586 {
3587 return defaultStorage->clearMasterPasswords();
3588 }
3589 catch ( const QgsNotSupportedException &e )
3590 {
3591 // It should not happen because we are checking the capability in advance
3593 return false;
3594 }
3595
3596 }
3597 else
3598 {
3599 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3600 return false;
3601 }
3602}
3603
3604const QString QgsAuthManager::masterPasswordCiv() const
3605{
3607
3608 if ( isDisabled() )
3609 return QString();
3610
3611 if ( QgsAuthConfigurationStorage *defaultStorage = firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability::ReadMasterPassword ) )
3612 {
3613 try
3614 {
3615 const QList<QgsAuthConfigurationStorage::MasterPasswordConfig> passwords { defaultStorage->masterPasswords( ) };
3616 if ( passwords.size() == 0 )
3617 {
3618 emit messageLog( tr( "Master password: FAILED to access database" ), authManTag(), Qgis::MessageLevel::Critical );
3619 return QString();
3620 }
3621 return passwords.first().civ;
3622 }
3623 catch ( const QgsNotSupportedException &e )
3624 {
3625 // It should not happen because we are checking the capability in advance
3627 return QString();
3628 }
3629 }
3630 else
3631 {
3632 emit messageLog( tr( "Could not connect to the default storage." ), authManTag(), Qgis::MessageLevel::Critical );
3633 return QString();
3634 }
3635}
3636
3637QStringList QgsAuthManager::configIds() const
3638{
3640
3641 QStringList configKeys = QStringList();
3642
3643 if ( isDisabled() )
3644 return configKeys;
3645
3646 // Loop through all storages with capability ReadConfiguration and get the config ids
3648
3649 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3650 {
3651 try
3652 {
3653 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigs();
3654 // Check if the config ids are already in the list
3655 for ( auto it = configs.cbegin(); it != configs.cend(); ++it )
3656 {
3657 if ( !configKeys.contains( it.key() ) )
3658 {
3659 configKeys.append( it.key() );
3660 }
3661 else
3662 {
3663 emit messageLog( tr( "Config id %1 is already in the list" ).arg( it.key() ), authManTag(), Qgis::MessageLevel::Warning );
3664 }
3665 }
3666 }
3667 catch ( const QgsNotSupportedException &e )
3668 {
3669 // It should not happen because we are checking the capability in advance
3671 }
3672 }
3673
3674 return configKeys;
3675}
3676
3677bool QgsAuthManager::verifyPasswordCanDecryptConfigs() const
3678{
3680
3681 if ( isDisabled() )
3682 return false;
3683
3684 // no need to check for setMasterPassword, since this is private and it will be set
3685
3686 // Loop through all storages with capability ReadConfiguration and check if the password can decrypt the configs
3688
3689 for ( const QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3690 {
3691
3692 if ( ! storage->isEncrypted() )
3693 {
3694 continue;
3695 }
3696
3697 try
3698 {
3699 const QgsAuthMethodConfigsMap configs = storage->authMethodConfigsWithPayload();
3700 for ( auto it = configs.cbegin(); it != configs.cend(); ++it )
3701 {
3702 QString configstring( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), it.value().config( QStringLiteral( "encrypted_payload" ) ) ) );
3703 if ( configstring.isEmpty() )
3704 {
3705 QgsDebugError( QStringLiteral( "Verify password can decrypt configs FAILED, could not decrypt a config (id: %1) from storage %2" )
3706 .arg( it.key(), storage->name() ) );
3707 return false;
3708 }
3709 }
3710 }
3711 catch ( const QgsNotSupportedException &e )
3712 {
3713 // It should not happen because we are checking the capability in advance
3715 return false;
3716 }
3717
3718 }
3719
3720 if ( storages.empty() )
3721 {
3722 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3723 return false;
3724 }
3725
3726 return true;
3727}
3728
3729bool QgsAuthManager::reencryptAllAuthenticationConfigs( const QString &prevpass, const QString &prevciv )
3730{
3732
3733 if ( isDisabled() )
3734 return false;
3735
3736 bool res = true;
3737 const QStringList ids = configIds();
3738 for ( const auto &configid : ids )
3739 {
3740 res = res && reencryptAuthenticationConfig( configid, prevpass, prevciv );
3741 }
3742 return res;
3743}
3744
3745bool QgsAuthManager::reencryptAuthenticationConfig( const QString &authcfg, const QString &prevpass, const QString &prevciv )
3746{
3748
3749 if ( isDisabled() )
3750 return false;
3751
3752 // no need to check for setMasterPassword, since this is private and it will be set
3753
3754 // Loop through all storages with capability ReadConfiguration and reencrypt the config
3756
3757 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3758 {
3759 try
3760 {
3761 if ( storage->methodConfigExists( authcfg ) )
3762 {
3763 if ( ! storage->isEncrypted() )
3764 {
3765 return true;
3766 }
3767
3768 QString payload;
3769 const QgsAuthMethodConfig config = storage->loadMethodConfig( authcfg, payload, true );
3770 if ( payload.isEmpty() || ! config.isValid( true ) )
3771 {
3772 QgsDebugError( QStringLiteral( "Reencrypt FAILED, could not find config (id: %1)" ).arg( authcfg ) );
3773 return false;
3774 }
3775
3776 QString configstring( QgsAuthCrypto::decrypt( prevpass, prevciv, payload ) );
3777 if ( configstring.isEmpty() )
3778 {
3779 QgsDebugError( QStringLiteral( "Reencrypt FAILED, could not decrypt config (id: %1)" ).arg( authcfg ) );
3780 return false;
3781 }
3782
3783 configstring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring );
3784
3785 if ( !storage->storeMethodConfig( config, configstring ) )
3786 {
3787 emit messageLog( tr( "Store config: FAILED to store config in default storage: %1" ).arg( storage->lastError() ), authManTag(), Qgis::MessageLevel::Warning );
3788 return false;
3789 }
3790 return true;
3791 }
3792 }
3793 catch ( const QgsNotSupportedException &e )
3794 {
3795 // It should not happen because we are checking the capability in advance
3797 return false;
3798 }
3799 }
3800
3801 if ( storages.empty() )
3802 {
3803 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3804 }
3805 else
3806 {
3807 emit messageLog( tr( "Reencrypt FAILED, could not find config (id: %1)" ).arg( authcfg ), authManTag(), Qgis::MessageLevel::Critical );
3808 }
3809
3810 return false;
3811}
3812
3813bool QgsAuthManager::reencryptAllAuthenticationSettings( const QString &prevpass, const QString &prevciv )
3814{
3816
3817 // TODO: start remove (when function is actually used)
3818 Q_UNUSED( prevpass )
3819 Q_UNUSED( prevciv )
3820 return true;
3821 // end remove
3822
3823#if 0
3824 if ( isDisabled() )
3825 return false;
3826
3828 // When adding settings that require encryption, add to list //
3830
3831 QStringList encryptedsettings;
3832 encryptedsettings << "";
3833
3834 for ( const auto & sett, std::as_const( encryptedsettings ) )
3835 {
3836 if ( sett.isEmpty() || !existsAuthSetting( sett ) )
3837 continue;
3838
3839 // no need to check for setMasterPassword, since this is private and it will be set
3840
3841 QSqlQuery query( authDbConnection() );
3842
3843 query.prepare( QStringLiteral( "SELECT value FROM %1 "
3844 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
3845
3846 query.bindValue( ":setting", sett );
3847
3848 if ( !authDbQuery( &query ) )
3849 return false;
3850
3851 if ( !query.isActive() || !query.isSelect() )
3852 {
3853 QgsDebugError( QStringLiteral( "Reencrypt FAILED, query not active or a select operation for setting: %2" ).arg( sett ) );
3854 return false;
3855 }
3856
3857 if ( query.first() )
3858 {
3859 QString settvalue( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
3860
3861 query.clear();
3862
3863 query.prepare( QStringLiteral( "UPDATE %1 "
3864 "SET value = :value "
3865 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
3866
3867 query.bindValue( ":setting", sett );
3868 query.bindValue( ":value", QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), settvalue ) );
3869
3870 if ( !authDbStartTransaction() )
3871 return false;
3872
3873 if ( !authDbQuery( &query ) )
3874 return false;
3875
3876 if ( !authDbCommit() )
3877 return false;
3878
3879 QgsDebugMsgLevel( QStringLiteral( "Reencrypt SUCCESS for setting: %2" ).arg( sett ), 2 );
3880 return true;
3881 }
3882 else
3883 {
3884 QgsDebugError( QStringLiteral( "Reencrypt FAILED, could not find in db setting: %2" ).arg( sett ) );
3885 return false;
3886 }
3887
3888 if ( query.next() )
3889 {
3890 QgsDebugError( QStringLiteral( "Select contains more than one for setting: %1" ).arg( sett ) );
3891 emit messageOut( tr( "Authentication database contains duplicate setting keys" ), authManTag(), WARNING );
3892 }
3893
3894 return false;
3895 }
3896
3897 return true;
3898#endif
3899}
3900
3901bool QgsAuthManager::reencryptAllAuthenticationIdentities( const QString &prevpass, const QString &prevciv )
3902{
3904
3905 if ( isDisabled() )
3906 return false;
3907
3908 bool res = true;
3909 const QStringList ids = certIdentityIds();
3910 for ( const auto &identid : ids )
3911 {
3912 res = res && reencryptAuthenticationIdentity( identid, prevpass, prevciv );
3913 }
3914 return res;
3915}
3916
3917bool QgsAuthManager::reencryptAuthenticationIdentity(
3918 const QString &identid,
3919 const QString &prevpass,
3920 const QString &prevciv )
3921{
3923
3924 if ( isDisabled() )
3925 return false;
3926
3927 // no need to check for setMasterPassword, since this is private and it will be set
3928
3929 // Loop through all storages with capability ReadCertificateIdentity and reencrypt the identity
3931
3932
3933 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
3934 {
3935
3936 try
3937 {
3938
3939 if ( storage->certIdentityExists( identid ) )
3940 {
3941 if ( ! storage->isEncrypted() )
3942 {
3943 return true;
3944 }
3945
3946 const QPair<QSslCertificate, QString> identityBundle = storage->loadCertIdentityBundle( identid );
3947 QString keystring( QgsAuthCrypto::decrypt( prevpass, prevciv, identityBundle.second ) );
3948 if ( keystring.isEmpty() )
3949 {
3950 QgsDebugError( QStringLiteral( "Reencrypt FAILED, could not decrypt identity id: %1" ).arg( identid ) );
3951 return false;
3952 }
3953
3954 keystring = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), keystring );
3955 return storage->storeCertIdentity( identityBundle.first, keystring );
3956 }
3957 }
3958 catch ( const QgsNotSupportedException &e )
3959 {
3960 // It should not happen because we are checking the capability in advance
3962 return false;
3963 }
3964 }
3965
3966 if ( storages.empty() )
3967 {
3968 emit messageLog( tr( "Could not connect to any authentication configuration storage." ), authManTag(), Qgis::MessageLevel::Critical );
3969 }
3970 else
3971 {
3972 emit messageLog( tr( "Reencrypt FAILED, could not find identity (id: %1)" ).arg( identid ), authManTag(), Qgis::MessageLevel::Critical );
3973 }
3974
3975 return false;
3976}
3977
3978void QgsAuthManager::insertCaCertInCache( QgsAuthCertUtils::CaCertSource source, const QList<QSslCertificate> &certs )
3979{
3981
3982 for ( const auto &cert : certs )
3983 {
3984 mCaCertsCache.insert( QgsAuthCertUtils::shaHexForCert( cert ),
3985 QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate>( source, cert ) );
3986 }
3987}
3988
3989QString QgsAuthManager::authPasswordHelperKeyName() const
3990{
3992
3993 QString dbProfilePath;
3994
3995 // TODO: get the current profile name from the application
3996
3997 if ( isFilesystemBasedDatabase( mAuthDatabaseConnectionUri ) )
3998 {
3999 const QFileInfo info( mAuthDatabaseConnectionUri );
4000 dbProfilePath = info.dir().dirName();
4001 }
4002 else
4003 {
4004 dbProfilePath = QCryptographicHash::hash( ( mAuthDatabaseConnectionUri.toUtf8() ), QCryptographicHash::Md5 ).toHex();
4005 }
4006
4007 // if not running from the default profile, ensure that a different key is used
4008 return AUTH_PASSWORD_HELPER_KEY_NAME_BASE + ( dbProfilePath.compare( QLatin1String( "default" ), Qt::CaseInsensitive ) == 0 ? QString() : dbProfilePath );
4009}
4010
4012{
4014 const auto storages = storageRegistry->readyStorages( );
4015 for ( QgsAuthConfigurationStorage *storage : std::as_const( storages ) )
4016 {
4017 if ( qobject_cast<QgsAuthConfigurationStorageDb *>( storage ) )
4018 {
4019 return static_cast<QgsAuthConfigurationStorageDb *>( storage );
4020 }
4021 }
4022 return nullptr;
4023}
4024
4025QgsAuthConfigurationStorage *QgsAuthManager::firstStorageWithCapability( Qgis::AuthConfigurationStorageCapability capability ) const
4026{
4028 return storageRegistry->firstReadyStorageWithCapability( capability );
4029}
4030
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:6494
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6493
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.