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