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