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