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