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