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