QGIS API Documentation 3.29.0-Master (53715c36dc)
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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( "QGIS_AUTH_PASSWORD_FILE set, but FAILED to set password using: " + passpath );
315 return false;
316 }
317 }
318 else
319 {
320 QgsDebugMsg( "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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( err );
1134 emit messageOut( tr( err ), authManTag(), WARNING );
1135 return false;
1136 }
1137#if( 0 )
1138 QgsDebugMsg( QStringLiteral( "authDbConfigTable(): %1" ).arg( authDbConfigTable() ) );
1139 QgsDebugMsg( QStringLiteral( "name: %1" ).arg( config.name() ) );
1140 QgsDebugMsg( QStringLiteral( "uri: %1" ).arg( config.uri() ) );
1141 QgsDebugMsg( QStringLiteral( "type: %1" ).arg( config.method() ) );
1142 QgsDebugMsg( QStringLiteral( "version: %1" ).arg( config.version() ) );
1143 QgsDebugMsg( QStringLiteral( "config: %1" ).arg( configstring ) ); // 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 QgsDebugMsg( 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 QgsDebugMsg( err );
1196 emit messageOut( tr( err ), authManTag(), WARNING );
1197 return false;
1198 }
1199
1200#if( 0 )
1201 QgsDebugMsg( QStringLiteral( "authDbConfigTable(): %1" ).arg( authDbConfigTable() ) );
1202 QgsDebugMsg( QStringLiteral( "id: %1" ).arg( config.id() ) );
1203 QgsDebugMsg( QStringLiteral( "name: %1" ).arg( config.name() ) );
1204 QgsDebugMsg( QStringLiteral( "uri: %1" ).arg( config.uri() ) );
1205 QgsDebugMsg( QStringLiteral( "type: %1" ).arg( config.method() ) );
1206 QgsDebugMsg( QStringLiteral( "version: %1" ).arg( config.version() ) );
1207 QgsDebugMsg( QStringLiteral( "config: %1" ).arg( configstring ) ); // 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( 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 QgsDebugMsg( err );
1582 emit messageOut( tr( err ), authManTag(), WARNING );
1583 return false;
1584 }
1585
1588 initSslCaches();
1589
1590 emit authDatabaseChanged();
1591
1592 return true;
1593}
1594
1595bool QgsAuthManager::updateNetworkRequest( QNetworkRequest &request, const QString &authcfg,
1596 const QString &dataprovider )
1597{
1598 if ( isDisabled() )
1599 return false;
1600
1601 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1602 if ( authmethod )
1603 {
1604 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkRequest ) )
1605 {
1606 QgsDebugMsg( QStringLiteral( "Network request updating not supported by authcfg: %1" ).arg( authcfg ) );
1607 return true;
1608 }
1609
1610 if ( !authmethod->updateNetworkRequest( request, authcfg, dataprovider.toLower() ) )
1611 {
1612 authmethod->clearCachedConfig( authcfg );
1613 return false;
1614 }
1615 return true;
1616 }
1617 return false;
1618}
1619
1620bool QgsAuthManager::updateNetworkReply( QNetworkReply *reply, const QString &authcfg,
1621 const QString &dataprovider )
1622{
1623 if ( isDisabled() )
1624 return false;
1625
1626 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1627 if ( authmethod )
1628 {
1629 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkReply ) )
1630 {
1631 QgsDebugMsg( QStringLiteral( "Network reply updating not supported by authcfg: %1" ).arg( authcfg ) );
1632 return true;
1633 }
1634
1635 if ( !authmethod->updateNetworkReply( reply, authcfg, dataprovider.toLower() ) )
1636 {
1637 authmethod->clearCachedConfig( authcfg );
1638 return false;
1639 }
1640 return true;
1641 }
1642
1643 return false;
1644}
1645
1646bool QgsAuthManager::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
1647 const QString &dataprovider )
1648{
1649 if ( isDisabled() )
1650 return false;
1651
1652 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1653 if ( authmethod )
1654 {
1655 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::DataSourceUri ) )
1656 {
1657 QgsDebugMsg( QStringLiteral( "Data source URI updating not supported by authcfg: %1" ).arg( authcfg ) );
1658 return true;
1659 }
1660
1661 if ( !authmethod->updateDataSourceUriItems( connectionItems, authcfg, dataprovider.toLower() ) )
1662 {
1663 authmethod->clearCachedConfig( authcfg );
1664 return false;
1665 }
1666 return true;
1667 }
1668
1669 return false;
1670}
1671
1672bool QgsAuthManager::updateNetworkProxy( QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider )
1673{
1674 if ( isDisabled() )
1675 return false;
1676
1677 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
1678 if ( authmethod )
1679 {
1680 if ( !( authmethod->supportedExpansions() & QgsAuthMethod::NetworkProxy ) )
1681 {
1682 QgsDebugMsg( QStringLiteral( "Proxy updating not supported by authcfg: %1" ).arg( authcfg ) );
1683 return true;
1684 }
1685
1686 if ( !authmethod->updateNetworkProxy( proxy, authcfg, dataprovider.toLower() ) )
1687 {
1688 authmethod->clearCachedConfig( authcfg );
1689 return false;
1690 }
1691 QgsDebugMsgLevel( QStringLiteral( "Proxy updated successfully from authcfg: %1" ).arg( authcfg ), 2 );
1692 return true;
1693 }
1694
1695 return false;
1696}
1697
1698bool QgsAuthManager::storeAuthSetting( const QString &key, const QVariant &value, bool encrypt )
1699{
1700 QMutexLocker locker( mMutex.get() );
1701 if ( key.isEmpty() )
1702 return false;
1703
1704 QString storeval( value.toString() );
1705 if ( encrypt )
1706 {
1707 if ( !setMasterPassword( true ) )
1708 {
1709 return false;
1710 }
1711 else
1712 {
1713 storeval = QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), value.toString() );
1714 }
1715 }
1716
1717 removeAuthSetting( key );
1718
1719 QSqlQuery query( authDatabaseConnection() );
1720 query.prepare( QStringLiteral( "INSERT INTO %1 (setting, value) "
1721 "VALUES (:setting, :value)" ).arg( authDbSettingsTable() ) );
1722
1723 query.bindValue( QStringLiteral( ":setting" ), key );
1724 query.bindValue( QStringLiteral( ":value" ), storeval );
1725
1726 if ( !authDbStartTransaction() )
1727 return false;
1728
1729 if ( !authDbQuery( &query ) )
1730 return false;
1731
1732 if ( !authDbCommit() )
1733 return false;
1734
1735 QgsDebugMsgLevel( QStringLiteral( "Store setting SUCCESS for key: %1" ).arg( key ), 2 );
1736 return true;
1737}
1738
1739QVariant QgsAuthManager::authSetting( const QString &key, const QVariant &defaultValue, bool decrypt )
1740{
1741 QMutexLocker locker( mMutex.get() );
1742 if ( key.isEmpty() )
1743 return QVariant();
1744
1745 if ( decrypt && !setMasterPassword( true ) )
1746 return QVariant();
1747
1748 QVariant value = defaultValue;
1749 QSqlQuery query( authDatabaseConnection() );
1750 query.prepare( QStringLiteral( "SELECT value FROM %1 "
1751 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
1752
1753 query.bindValue( QStringLiteral( ":setting" ), key );
1754
1755 if ( !authDbQuery( &query ) )
1756 return QVariant();
1757
1758 if ( query.isActive() && query.isSelect() )
1759 {
1760 if ( query.first() )
1761 {
1762 if ( decrypt )
1763 {
1764 value = QVariant( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), query.value( 0 ).toString() ) );
1765 }
1766 else
1767 {
1768 value = query.value( 0 );
1769 }
1770 QgsDebugMsgLevel( QStringLiteral( "Authentication setting retrieved for key: %1" ).arg( key ), 2 );
1771 }
1772 if ( query.next() )
1773 {
1774 QgsDebugMsg( QStringLiteral( "Select contains more than one for setting key: %1" ).arg( key ) );
1775 emit messageOut( tr( "Authentication database contains duplicate settings" ), authManTag(), WARNING );
1776 return QVariant();
1777 }
1778 }
1779 return value;
1780}
1781
1782bool QgsAuthManager::existsAuthSetting( const QString &key )
1783{
1784 QMutexLocker locker( mMutex.get() );
1785 if ( key.isEmpty() )
1786 return false;
1787
1788 QSqlQuery query( authDatabaseConnection() );
1789 query.prepare( QStringLiteral( "SELECT value FROM %1 "
1790 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
1791
1792 query.bindValue( QStringLiteral( ":setting" ), key );
1793
1794 if ( !authDbQuery( &query ) )
1795 return false;
1796
1797 bool res = false;
1798 if ( query.isActive() && query.isSelect() )
1799 {
1800 if ( query.first() )
1801 {
1802 QgsDebugMsgLevel( QStringLiteral( "Authentication setting exists for key: %1" ).arg( key ), 2 );
1803 res = true;
1804 }
1805 if ( query.next() )
1806 {
1807 QgsDebugMsg( QStringLiteral( "Select contains more than one for setting key: %1" ).arg( key ) );
1808 emit messageOut( tr( "Authentication database contains duplicate settings" ), authManTag(), WARNING );
1809 return false;
1810 }
1811 }
1812 return res;
1813}
1814
1815bool QgsAuthManager::removeAuthSetting( const QString &key )
1816{
1817 QMutexLocker locker( mMutex.get() );
1818 if ( key.isEmpty() )
1819 return false;
1820
1821 QSqlQuery query( authDatabaseConnection() );
1822
1823 query.prepare( QStringLiteral( "DELETE FROM %1 WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
1824
1825 query.bindValue( QStringLiteral( ":setting" ), key );
1826
1827 if ( !authDbStartTransaction() )
1828 return false;
1829
1830 if ( !authDbQuery( &query ) )
1831 return false;
1832
1833 if ( !authDbCommit() )
1834 return false;
1835
1836 QgsDebugMsgLevel( QStringLiteral( "REMOVED setting for key: %1" ).arg( key ), 2 );
1837
1838 return true;
1839}
1840
1841
1842#ifndef QT_NO_SSL
1843
1845
1847{
1848 QgsScopedRuntimeProfile profile( "Initialize SSL cache" );
1849
1850 QMutexLocker locker( mMutex.get() );
1851 bool res = true;
1852 res = res && rebuildCaCertsCache();
1853 res = res && rebuildCertTrustCache();
1854 res = res && rebuildTrustedCaCertsCache();
1855 res = res && rebuildIgnoredSslErrorCache();
1856 mCustomConfigByHostCache.clear();
1857 mHasCheckedIfCustomConfigByHostExists = false;
1858
1859 if ( !res )
1860 QgsDebugMsg( QStringLiteral( "Init of SSL caches FAILED" ) );
1861 return res;
1862}
1863
1864bool QgsAuthManager::storeCertIdentity( const QSslCertificate &cert, const QSslKey &key )
1865{
1866 QMutexLocker locker( mMutex.get() );
1867 if ( cert.isNull() )
1868 {
1869 QgsDebugMsg( QStringLiteral( "Passed certificate is null" ) );
1870 return false;
1871 }
1872 if ( key.isNull() )
1873 {
1874 QgsDebugMsg( QStringLiteral( "Passed private key is null" ) );
1875 return false;
1876 }
1877
1878 if ( !setMasterPassword( true ) )
1879 return false;
1880
1881 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
1882 removeCertIdentity( id );
1883
1884 QString certpem( cert.toPem() );
1885 QString keypem( QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), key.toPem() ) );
1886
1887 QSqlQuery query( authDatabaseConnection() );
1888 query.prepare( QStringLiteral( "INSERT INTO %1 (id, key, cert) "
1889 "VALUES (:id, :key, :cert)" ).arg( authDbIdentitiesTable() ) );
1890
1891 query.bindValue( QStringLiteral( ":id" ), id );
1892 query.bindValue( QStringLiteral( ":key" ), keypem );
1893 query.bindValue( QStringLiteral( ":cert" ), certpem );
1894
1895 if ( !authDbStartTransaction() )
1896 return false;
1897
1898 if ( !authDbQuery( &query ) )
1899 return false;
1900
1901 if ( !authDbCommit() )
1902 return false;
1903
1904 QgsDebugMsgLevel( QStringLiteral( "Store certificate identity SUCCESS for id: %1" ).arg( id ), 2 );
1905 return true;
1906}
1907
1908const QSslCertificate QgsAuthManager::certIdentity( const QString &id )
1909{
1910 QMutexLocker locker( mMutex.get() );
1911 QSslCertificate emptycert;
1912 QSslCertificate cert;
1913 if ( id.isEmpty() )
1914 return emptycert;
1915
1916 QSqlQuery query( authDatabaseConnection() );
1917 query.prepare( QStringLiteral( "SELECT cert FROM %1 "
1918 "WHERE id = :id" ).arg( authDbIdentitiesTable() ) );
1919
1920 query.bindValue( QStringLiteral( ":id" ), id );
1921
1922 if ( !authDbQuery( &query ) )
1923 return emptycert;
1924
1925 if ( query.isActive() && query.isSelect() )
1926 {
1927 if ( query.first() )
1928 {
1929 cert = QSslCertificate( query.value( 0 ).toByteArray(), QSsl::Pem );
1930 QgsDebugMsgLevel( QStringLiteral( "Certificate identity retrieved for id: %1" ).arg( id ), 2 );
1931 }
1932 if ( query.next() )
1933 {
1934 QgsDebugMsg( QStringLiteral( "Select contains more than one certificate identity for id: %1" ).arg( id ) );
1935 emit messageOut( tr( "Authentication database contains duplicate certificate identity" ), authManTag(), WARNING );
1936 return emptycert;
1937 }
1938 }
1939 return cert;
1940}
1941
1942const QPair<QSslCertificate, QSslKey> QgsAuthManager::certIdentityBundle( const QString &id )
1943{
1944 QMutexLocker locker( mMutex.get() );
1945 QPair<QSslCertificate, QSslKey> bundle;
1946 if ( id.isEmpty() )
1947 return bundle;
1948
1949 if ( !setMasterPassword( true ) )
1950 return bundle;
1951
1952 QSqlQuery query( authDatabaseConnection() );
1953 query.prepare( QStringLiteral( "SELECT key, cert FROM %1 "
1954 "WHERE id = :id" ).arg( authDbIdentitiesTable() ) );
1955
1956 query.bindValue( QStringLiteral( ":id" ), id );
1957
1958 if ( !authDbQuery( &query ) )
1959 return bundle;
1960
1961 if ( query.isActive() && query.isSelect() )
1962 {
1963 QSslCertificate cert;
1964 QSslKey key;
1965 if ( query.first() )
1966 {
1967 key = QSslKey( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), query.value( 0 ).toString() ).toLatin1(),
1968 QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey );
1969 if ( key.isNull() )
1970 {
1971 const char *err = QT_TR_NOOP( "Retrieve certificate identity bundle: FAILED to create private key" );
1972 QgsDebugMsg( err );
1973 emit messageOut( tr( err ), authManTag(), WARNING );
1974 return bundle;
1975 }
1976 cert = QSslCertificate( query.value( 1 ).toByteArray(), QSsl::Pem );
1977 if ( cert.isNull() )
1978 {
1979 const char *err = QT_TR_NOOP( "Retrieve certificate identity bundle: FAILED to create certificate" );
1980 QgsDebugMsg( err );
1981 emit messageOut( tr( err ), authManTag(), WARNING );
1982 return bundle;
1983 }
1984 QgsDebugMsgLevel( QStringLiteral( "Certificate identity bundle retrieved for id: %1" ).arg( id ), 2 );
1985 }
1986 if ( query.next() )
1987 {
1988 QgsDebugMsg( QStringLiteral( "Select contains more than one certificate identity for id: %1" ).arg( id ) );
1989 emit messageOut( tr( "Authentication database contains duplicate certificate identity" ), authManTag(), WARNING );
1990 return bundle;
1991 }
1992 bundle = qMakePair( cert, key );
1993 }
1994 return bundle;
1995}
1996
1997const QStringList QgsAuthManager::certIdentityBundleToPem( const QString &id )
1998{
1999 QMutexLocker locker( mMutex.get() );
2000 QPair<QSslCertificate, QSslKey> bundle( certIdentityBundle( id ) );
2001 if ( QgsAuthCertUtils::certIsViable( bundle.first ) && !bundle.second.isNull() )
2002 {
2003 return QStringList() << QString( bundle.first.toPem() ) << QString( bundle.second.toPem() );
2004 }
2005 return QStringList();
2006}
2007
2008const QList<QSslCertificate> QgsAuthManager::certIdentities()
2009{
2010 QMutexLocker locker( mMutex.get() );
2011 QList<QSslCertificate> certs;
2012
2013 QSqlQuery query( authDatabaseConnection() );
2014 query.prepare( QStringLiteral( "SELECT id, cert FROM %1" ).arg( authDbIdentitiesTable() ) );
2015
2016 if ( !authDbQuery( &query ) )
2017 return certs;
2018
2019 if ( query.isActive() && query.isSelect() )
2020 {
2021 while ( query.next() )
2022 {
2023 certs << QSslCertificate( query.value( 1 ).toByteArray(), QSsl::Pem );
2024 }
2025 }
2026
2027 return certs;
2028}
2029
2031{
2032 QMutexLocker locker( mMutex.get() );
2033 QStringList identityids = QStringList();
2034
2035 if ( isDisabled() )
2036 return identityids;
2037
2038 QSqlQuery query( authDatabaseConnection() );
2039 query.prepare( QStringLiteral( "SELECT id FROM %1" ).arg( authDbIdentitiesTable() ) );
2040
2041 if ( !authDbQuery( &query ) )
2042 {
2043 return identityids;
2044 }
2045
2046 if ( query.isActive() )
2047 {
2048 while ( query.next() )
2049 {
2050 identityids << query.value( 0 ).toString();
2051 }
2052 }
2053 return identityids;
2054}
2055
2056bool QgsAuthManager::existsCertIdentity( const QString &id )
2057{
2058 QMutexLocker locker( mMutex.get() );
2059 if ( id.isEmpty() )
2060 return false;
2061
2062 QSqlQuery query( authDatabaseConnection() );
2063 query.prepare( QStringLiteral( "SELECT cert FROM %1 "
2064 "WHERE id = :id" ).arg( authDbIdentitiesTable() ) );
2065
2066 query.bindValue( QStringLiteral( ":id" ), id );
2067
2068 if ( !authDbQuery( &query ) )
2069 return false;
2070
2071 bool res = false;
2072 if ( query.isActive() && query.isSelect() )
2073 {
2074 if ( query.first() )
2075 {
2076 QgsDebugMsgLevel( QStringLiteral( "Certificate bundle exists for id: %1" ).arg( id ), 2 );
2077 res = true;
2078 }
2079 if ( query.next() )
2080 {
2081 QgsDebugMsg( QStringLiteral( "Select contains more than one certificate bundle for id: %1" ).arg( id ) );
2082 emit messageOut( tr( "Authentication database contains duplicate certificate bundles" ), authManTag(), WARNING );
2083 return false;
2084 }
2085 }
2086 return res;
2087}
2088
2089bool QgsAuthManager::removeCertIdentity( const QString &id )
2090{
2091 QMutexLocker locker( mMutex.get() );
2092 if ( id.isEmpty() )
2093 {
2094 QgsDebugMsg( QStringLiteral( "Passed bundle ID is empty" ) );
2095 return false;
2096 }
2097
2098 QSqlQuery query( authDatabaseConnection() );
2099
2100 query.prepare( QStringLiteral( "DELETE FROM %1 WHERE id = :id" ).arg( authDbIdentitiesTable() ) );
2101
2102 query.bindValue( QStringLiteral( ":id" ), id );
2103
2104 if ( !authDbStartTransaction() )
2105 return false;
2106
2107 if ( !authDbQuery( &query ) )
2108 return false;
2109
2110 if ( !authDbCommit() )
2111 return false;
2112
2113 QgsDebugMsgLevel( QStringLiteral( "REMOVED certificate identity for id: %1" ).arg( id ), 2 );
2114 return true;
2115}
2116
2118{
2119 QMutexLocker locker( mMutex.get() );
2120 if ( config.isNull() )
2121 {
2122 QgsDebugMsg( QStringLiteral( "Passed config is null" ) );
2123 return false;
2124 }
2125
2126 QSslCertificate cert( config.sslCertificate() );
2127
2128 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2129 removeSslCertCustomConfig( id, config.sslHostPort().trimmed() );
2130
2131 QString certpem( cert.toPem() );
2132
2133 QSqlQuery query( authDatabaseConnection() );
2134 query.prepare( QStringLiteral( "INSERT OR REPLACE INTO %1 (id, host, cert, config) "
2135 "VALUES (:id, :host, :cert, :config)" ).arg( authDatabaseServersTable() ) );
2136
2137 query.bindValue( QStringLiteral( ":id" ), id );
2138 query.bindValue( QStringLiteral( ":host" ), config.sslHostPort().trimmed() );
2139 query.bindValue( QStringLiteral( ":cert" ), certpem );
2140 query.bindValue( QStringLiteral( ":config" ), config.configString() );
2141
2142 if ( !authDbStartTransaction() )
2143 return false;
2144
2145 if ( !authDbQuery( &query ) )
2146 return false;
2147
2148 if ( !authDbCommit() )
2149 return false;
2150
2151 QgsDebugMsgLevel( QStringLiteral( "Store SSL cert custom config SUCCESS for host:port, id: %1, %2" )
2152 .arg( config.sslHostPort().trimmed(), id ), 2 );
2153
2155 mHasCheckedIfCustomConfigByHostExists = false;
2156 mCustomConfigByHostCache.clear();
2157
2158 return true;
2159}
2160
2161const QgsAuthConfigSslServer QgsAuthManager::sslCertCustomConfig( const QString &id, const QString &hostport )
2162{
2163 QMutexLocker locker( mMutex.get() );
2165
2166 if ( id.isEmpty() || hostport.isEmpty() )
2167 {
2168 QgsDebugMsg( QStringLiteral( "Passed config ID or host:port is empty" ) );
2169 return config;
2170 }
2171
2172 QSqlQuery query( authDatabaseConnection() );
2173 query.prepare( QStringLiteral( "SELECT id, host, cert, config FROM %1 "
2174 "WHERE id = :id AND host = :host" ).arg( authDatabaseServersTable() ) );
2175
2176 query.bindValue( QStringLiteral( ":id" ), id );
2177 query.bindValue( QStringLiteral( ":host" ), hostport.trimmed() );
2178
2179 if ( !authDbQuery( &query ) )
2180 return config;
2181
2182 if ( query.isActive() && query.isSelect() )
2183 {
2184 if ( query.first() )
2185 {
2186 config.setSslCertificate( QSslCertificate( query.value( 2 ).toByteArray(), QSsl::Pem ) );
2187 config.setSslHostPort( query.value( 1 ).toString().trimmed() );
2188 config.loadConfigString( query.value( 3 ).toString() );
2189 QgsDebugMsgLevel( QStringLiteral( "SSL cert custom config retrieved for host:port, id: %1, %2" ).arg( hostport, id ), 2 );
2190 }
2191 if ( query.next() )
2192 {
2193 QgsDebugMsg( QStringLiteral( "Select contains more than one SSL cert custom config for host:port, id: %1, %2" ).arg( hostport, id ) );
2194 emit messageOut( tr( "Authentication database contains duplicate SSL cert custom configs for host:port, id: %1, %2" )
2195 .arg( hostport, id ), authManTag(), WARNING );
2196 QgsAuthConfigSslServer emptyconfig;
2197 return emptyconfig;
2198 }
2199 }
2200 return config;
2201}
2202
2204{
2206 if ( hostport.isEmpty() )
2207 {
2208 return config;
2209 }
2210
2211 QMutexLocker locker( mMutex.get() );
2212 if ( mHasCheckedIfCustomConfigByHostExists && !mHasCustomConfigByHost )
2213 return config;
2214 if ( mCustomConfigByHostCache.contains( hostport ) )
2215 return mCustomConfigByHostCache.value( hostport );
2216
2217 QSqlQuery query( authDatabaseConnection() );
2218
2219 // first run -- see if we have ANY custom config by host. If not, we can skip all future checks for any host
2220 if ( !mHasCheckedIfCustomConfigByHostExists )
2221 {
2222 mHasCheckedIfCustomConfigByHostExists = true;
2223 query.prepare( QString( "SELECT count(*) FROM %1" ).arg( authDatabaseServersTable() ) );
2224 if ( !authDbQuery( &query ) )
2225 {
2226 mHasCustomConfigByHost = false;
2227 return config;
2228 }
2229 if ( query.isActive() && query.isSelect() && query.first() )
2230 {
2231 mHasCustomConfigByHost = query.value( 0 ).toInt() > 0;
2232 if ( !mHasCustomConfigByHost )
2233 return config;
2234 }
2235 else
2236 {
2237 mHasCustomConfigByHost = false;
2238 return config;
2239 }
2240 }
2241
2242 query.prepare( QString( "SELECT id, host, cert, config FROM %1 "
2243 "WHERE host = :host" ).arg( authDatabaseServersTable() ) );
2244
2245 query.bindValue( QStringLiteral( ":host" ), hostport.trimmed() );
2246
2247 if ( !authDbQuery( &query ) )
2248 {
2249 mCustomConfigByHostCache.insert( hostport, config );
2250 return config;
2251 }
2252
2253 if ( query.isActive() && query.isSelect() )
2254 {
2255 if ( query.first() )
2256 {
2257 config.setSslCertificate( QSslCertificate( query.value( 2 ).toByteArray(), QSsl::Pem ) );
2258 config.setSslHostPort( query.value( 1 ).toString().trimmed() );
2259 config.loadConfigString( query.value( 3 ).toString() );
2260 QgsDebugMsgLevel( QStringLiteral( "SSL cert custom config retrieved for host:port: %1" ).arg( hostport ), 2 );
2261 }
2262 if ( query.next() )
2263 {
2264 QgsDebugMsg( QStringLiteral( "Select contains more than one SSL cert custom config for host:port: %1" ).arg( hostport ) );
2265 emit messageOut( tr( "Authentication database contains duplicate SSL cert custom configs for host:port: %1" )
2266 .arg( hostport ), authManTag(), WARNING );
2267 QgsAuthConfigSslServer emptyconfig;
2268 mCustomConfigByHostCache.insert( hostport, emptyconfig );
2269 return emptyconfig;
2270 }
2271 }
2272
2273 mCustomConfigByHostCache.insert( hostport, config );
2274 return config;
2275}
2276
2277const QList<QgsAuthConfigSslServer> QgsAuthManager::sslCertCustomConfigs()
2278{
2279 QMutexLocker locker( mMutex.get() );
2280 QList<QgsAuthConfigSslServer> configs;
2281
2282 QSqlQuery query( authDatabaseConnection() );
2283 query.prepare( QStringLiteral( "SELECT id, host, cert, config FROM %1" ).arg( authDatabaseServersTable() ) );
2284
2285 if ( !authDbQuery( &query ) )
2286 return configs;
2287
2288 if ( query.isActive() && query.isSelect() )
2289 {
2290 while ( query.next() )
2291 {
2293 config.setSslCertificate( QSslCertificate( query.value( 2 ).toByteArray(), QSsl::Pem ) );
2294 config.setSslHostPort( query.value( 1 ).toString().trimmed() );
2295 config.loadConfigString( query.value( 3 ).toString() );
2296
2297 configs.append( config );
2298 }
2299 }
2300
2301 return configs;
2302}
2303
2304bool QgsAuthManager::existsSslCertCustomConfig( const QString &id, const QString &hostport )
2305{
2306 QMutexLocker locker( mMutex.get() );
2307 if ( id.isEmpty() || hostport.isEmpty() )
2308 {
2309 QgsDebugMsg( QStringLiteral( "Passed config ID or host:port is empty" ) );
2310 return false;
2311 }
2312
2313 QSqlQuery query( authDatabaseConnection() );
2314 query.prepare( QStringLiteral( "SELECT cert FROM %1 "
2315 "WHERE id = :id AND host = :host" ).arg( authDatabaseServersTable() ) );
2316
2317 query.bindValue( QStringLiteral( ":id" ), id );
2318 query.bindValue( QStringLiteral( ":host" ), hostport.trimmed() );
2319
2320 if ( !authDbQuery( &query ) )
2321 return false;
2322
2323 bool res = false;
2324 if ( query.isActive() && query.isSelect() )
2325 {
2326 if ( query.first() )
2327 {
2328 QgsDebugMsgLevel( QStringLiteral( "SSL cert custom config exists for host:port, id: %1, %2" ).arg( hostport, id ), 2 );
2329 res = true;
2330 }
2331 if ( query.next() )
2332 {
2333 QgsDebugMsg( QStringLiteral( "Select contains more than one SSL cert custom config for host:port, id: %1, %2" ).arg( hostport, id ) );
2334 emit messageOut( tr( "Authentication database contains duplicate SSL cert custom configs for host:port, id: %1, %2" )
2335 .arg( hostport, id ), authManTag(), WARNING );
2336 return false;
2337 }
2338 }
2339 return res;
2340}
2341
2342bool QgsAuthManager::removeSslCertCustomConfig( const QString &id, const QString &hostport )
2343{
2344 QMutexLocker locker( mMutex.get() );
2345 if ( id.isEmpty() || hostport.isEmpty() )
2346 {
2347 QgsDebugMsg( QStringLiteral( "Passed config ID or host:port is empty" ) );
2348 return false;
2349 }
2350
2351 mHasCheckedIfCustomConfigByHostExists = false;
2352 mCustomConfigByHostCache.clear();
2353
2354 QSqlQuery query( authDatabaseConnection() );
2355
2356 query.prepare( QStringLiteral( "DELETE FROM %1 WHERE id = :id AND host = :host" ).arg( authDatabaseServersTable() ) );
2357
2358 query.bindValue( QStringLiteral( ":id" ), id );
2359 query.bindValue( QStringLiteral( ":host" ), hostport.trimmed() );
2360
2361 if ( !authDbStartTransaction() )
2362 return false;
2363
2364 if ( !authDbQuery( &query ) )
2365 return false;
2366
2367 if ( !authDbCommit() )
2368 return false;
2369
2370 QString shahostport( QStringLiteral( "%1:%2" ).arg( id, hostport ) );
2371 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2372 {
2373 mIgnoredSslErrorsCache.remove( shahostport );
2374 }
2375
2376 QgsDebugMsgLevel( QStringLiteral( "REMOVED SSL cert custom config for host:port, id: %1, %2" ).arg( hostport, id ), 2 );
2378 return true;
2379}
2380
2382{
2383 QMutexLocker locker( mMutex.get() );
2384 if ( !mIgnoredSslErrorsCache.isEmpty() )
2385 {
2386 QgsDebugMsg( QStringLiteral( "Ignored SSL errors cache items:" ) );
2387 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = mIgnoredSslErrorsCache.constBegin();
2388 while ( i != mIgnoredSslErrorsCache.constEnd() )
2389 {
2390 QStringList errs;
2391 for ( auto err : i.value() )
2392 {
2394 }
2395 QgsDebugMsg( QStringLiteral( "%1 = %2" ).arg( i.key(), errs.join( ", " ) ) );
2396 ++i;
2397 }
2398 }
2399 else
2400 {
2401 QgsDebugMsgLevel( QStringLiteral( "Ignored SSL errors cache EMPTY" ), 2 );
2402 }
2403}
2404
2406{
2407 QMutexLocker locker( mMutex.get() );
2408 if ( config.isNull() )
2409 {
2410 QgsDebugMsg( QStringLiteral( "Passed config is null" ) );
2411 return false;
2412 }
2413
2414 QString shahostport( QStringLiteral( "%1:%2" )
2415 .arg( QgsAuthCertUtils::shaHexForCert( config.sslCertificate() ).trimmed(),
2416 config.sslHostPort().trimmed() ) );
2417 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2418 {
2419 mIgnoredSslErrorsCache.remove( shahostport );
2420 }
2421 const QList<QSslError::SslError> errenums( config.sslIgnoredErrorEnums() );
2422 if ( !errenums.isEmpty() )
2423 {
2424 mIgnoredSslErrorsCache.insert( shahostport, QSet<QSslError::SslError>( errenums.begin(), errenums.end() ) );
2425 QgsDebugMsgLevel( QStringLiteral( "Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1" ).arg( shahostport ), 2 );
2427 return true;
2428 }
2429
2430 QgsDebugMsgLevel( QStringLiteral( "No ignored SSL errors to cache for sha:host:port = %1" ).arg( shahostport ), 2 );
2431 return true;
2432}
2433
2434bool QgsAuthManager::updateIgnoredSslErrorsCache( const QString &shahostport, const QList<QSslError> &errors )
2435{
2436 QMutexLocker locker( mMutex.get() );
2437 const thread_local QRegularExpression rx( QRegularExpression::anchoredPattern( "\\S+:\\S+:\\d+" ) );
2438 if ( !rx.match( shahostport ).hasMatch() )
2439 {
2440 QgsDebugMsg( "Passed shahostport does not match \\S+:\\S+:\\d+, "
2441 "e.g. 74a4ef5ea94512a43769b744cda0ca5049a72491:www.example.com:443" );
2442 return false;
2443 }
2444
2445 if ( mIgnoredSslErrorsCache.contains( shahostport ) )
2446 {
2447 mIgnoredSslErrorsCache.remove( shahostport );
2448 }
2449
2450 if ( errors.isEmpty() )
2451 {
2452 QgsDebugMsg( QStringLiteral( "Passed errors list empty" ) );
2453 return false;
2454 }
2455
2456 QSet<QSslError::SslError> errs;
2457 for ( const auto &error : errors )
2458 {
2459 if ( error.error() == QSslError::NoError )
2460 continue;
2461
2462 errs.insert( error.error() );
2463 }
2464
2465 if ( errs.isEmpty() )
2466 {
2467 QgsDebugMsg( QStringLiteral( "Passed errors list does not contain errors" ) );
2468 return false;
2469 }
2470
2471 mIgnoredSslErrorsCache.insert( shahostport, errs );
2472
2473 QgsDebugMsgLevel( QStringLiteral( "Update of ignored SSL errors cache SUCCEEDED for sha:host:port = %1" ).arg( shahostport ), 2 );
2475 return true;
2476}
2477
2479{
2480 QMutexLocker locker( mMutex.get() );
2481 QHash<QString, QSet<QSslError::SslError> > prevcache( mIgnoredSslErrorsCache );
2482 QHash<QString, QSet<QSslError::SslError> > nextcache;
2483
2484 QSqlQuery query( authDatabaseConnection() );
2485 query.prepare( QStringLiteral( "SELECT id, host, config FROM %1" ).arg( authDatabaseServersTable() ) );
2486
2487 if ( !authDbQuery( &query ) )
2488 {
2489 QgsDebugMsg( QStringLiteral( "Rebuild of ignored SSL errors cache FAILED" ) );
2490 return false;
2491 }
2492
2493 if ( query.isActive() && query.isSelect() )
2494 {
2495 while ( query.next() )
2496 {
2497 QString shahostport( QStringLiteral( "%1:%2" )
2498 .arg( query.value( 0 ).toString().trimmed(),
2499 query.value( 1 ).toString().trimmed() ) );
2501 config.loadConfigString( query.value( 2 ).toString() );
2502 const QList<QSslError::SslError> errenums( config.sslIgnoredErrorEnums() );
2503 if ( !errenums.isEmpty() )
2504 {
2505 nextcache.insert( shahostport, QSet<QSslError::SslError>( errenums.begin(), errenums.end() ) );
2506 }
2507 if ( prevcache.contains( shahostport ) )
2508 {
2509 prevcache.remove( shahostport );
2510 }
2511 }
2512 }
2513
2514 if ( !prevcache.isEmpty() )
2515 {
2516 // preserve any existing per-session ignored errors for hosts
2517 QHash<QString, QSet<QSslError::SslError> >::const_iterator i = prevcache.constBegin();
2518 while ( i != prevcache.constEnd() )
2519 {
2520 nextcache.insert( i.key(), i.value() );
2521 ++i;
2522 }
2523 }
2524
2525 if ( nextcache != mIgnoredSslErrorsCache )
2526 {
2527 mIgnoredSslErrorsCache.clear();
2528 mIgnoredSslErrorsCache = nextcache;
2529 QgsDebugMsgLevel( QStringLiteral( "Rebuild of ignored SSL errors cache SUCCEEDED" ), 2 );
2531 return true;
2532 }
2533
2534 QgsDebugMsgLevel( QStringLiteral( "Rebuild of ignored SSL errors cache SAME AS BEFORE" ), 2 );
2536 return true;
2537}
2538
2539
2540bool QgsAuthManager::storeCertAuthorities( const QList<QSslCertificate> &certs )
2541{
2542 QMutexLocker locker( mMutex.get() );
2543 if ( certs.isEmpty() )
2544 {
2545 QgsDebugMsg( QStringLiteral( "Passed certificate list has no certs" ) );
2546 return false;
2547 }
2548
2549 for ( const auto &cert : certs )
2550 {
2551 if ( !storeCertAuthority( cert ) )
2552 return false;
2553 }
2554 return true;
2555}
2556
2557bool QgsAuthManager::storeCertAuthority( const QSslCertificate &cert )
2558{
2559 QMutexLocker locker( mMutex.get() );
2560 // don't refuse !cert.isValid() (actually just expired) CAs,
2561 // as user may want to ignore that SSL connection error
2562 if ( cert.isNull() )
2563 {
2564 QgsDebugMsg( QStringLiteral( "Passed certificate is null" ) );
2565 return false;
2566 }
2567
2568 removeCertAuthority( cert );
2569
2570 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2571 QString pem( cert.toPem() );
2572
2573 QSqlQuery query( authDatabaseConnection() );
2574 query.prepare( QStringLiteral( "INSERT INTO %1 (id, cert) "
2575 "VALUES (:id, :cert)" ).arg( authDbAuthoritiesTable() ) );
2576
2577 query.bindValue( QStringLiteral( ":id" ), id );
2578 query.bindValue( QStringLiteral( ":cert" ), pem );
2579
2580 if ( !authDbStartTransaction() )
2581 return false;
2582
2583 if ( !authDbQuery( &query ) )
2584 return false;
2585
2586 if ( !authDbCommit() )
2587 return false;
2588
2589 QgsDebugMsgLevel( QStringLiteral( "Store certificate authority SUCCESS for id: %1" ).arg( id ), 2 );
2590 return true;
2591}
2592
2593const QSslCertificate QgsAuthManager::certAuthority( const QString &id )
2594{
2595 QMutexLocker locker( mMutex.get() );
2596 QSslCertificate emptycert;
2597 QSslCertificate cert;
2598 if ( id.isEmpty() )
2599 return emptycert;
2600
2601 QSqlQuery query( authDatabaseConnection() );
2602 query.prepare( QStringLiteral( "SELECT cert FROM %1 "
2603 "WHERE id = :id" ).arg( authDbAuthoritiesTable() ) );
2604
2605 query.bindValue( QStringLiteral( ":id" ), id );
2606
2607 if ( !authDbQuery( &query ) )
2608 return emptycert;
2609
2610 if ( query.isActive() && query.isSelect() )
2611 {
2612 if ( query.first() )
2613 {
2614 cert = QSslCertificate( query.value( 0 ).toByteArray(), QSsl::Pem );
2615 QgsDebugMsgLevel( QStringLiteral( "Certificate authority retrieved for id: %1" ).arg( id ), 2 );
2616 }
2617 if ( query.next() )
2618 {
2619 QgsDebugMsg( QStringLiteral( "Select contains more than one certificate authority for id: %1" ).arg( id ) );
2620 emit messageOut( tr( "Authentication database contains duplicate certificate authorities" ), authManTag(), WARNING );
2621 return emptycert;
2622 }
2623 }
2624 return cert;
2625}
2626
2627bool QgsAuthManager::existsCertAuthority( const QSslCertificate &cert )
2628{
2629 QMutexLocker locker( mMutex.get() );
2630 if ( cert.isNull() )
2631 {
2632 QgsDebugMsg( QStringLiteral( "Passed certificate is null" ) );
2633 return false;
2634 }
2635
2636 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2637
2638 QSqlQuery query( authDatabaseConnection() );
2639 query.prepare( QStringLiteral( "SELECT value FROM %1 "
2640 "WHERE id = :id" ).arg( authDbAuthoritiesTable() ) );
2641
2642 query.bindValue( QStringLiteral( ":id" ), id );
2643
2644 if ( !authDbQuery( &query ) )
2645 return false;
2646
2647 bool res = false;
2648 if ( query.isActive() && query.isSelect() )
2649 {
2650 if ( query.first() )
2651 {
2652 QgsDebugMsgLevel( QStringLiteral( "Certificate authority exists for id: %1" ).arg( id ), 2 );
2653 res = true;
2654 }
2655 if ( query.next() )
2656 {
2657 QgsDebugMsg( QStringLiteral( "Select contains more than one certificate authority for id: %1" ).arg( id ) );
2658 emit messageOut( tr( "Authentication database contains duplicate certificate authorities" ), authManTag(), WARNING );
2659 return false;
2660 }
2661 }
2662 return res;
2663}
2664
2665bool QgsAuthManager::removeCertAuthority( const QSslCertificate &cert )
2666{
2667 QMutexLocker locker( mMutex.get() );
2668 if ( cert.isNull() )
2669 {
2670 QgsDebugMsg( QStringLiteral( "Passed certificate is null" ) );
2671 return false;
2672 }
2673
2674 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2675
2676 QSqlQuery query( authDatabaseConnection() );
2677
2678 query.prepare( QStringLiteral( "DELETE FROM %1 WHERE id = :id" ).arg( authDbAuthoritiesTable() ) );
2679
2680 query.bindValue( QStringLiteral( ":id" ), id );
2681
2682 if ( !authDbStartTransaction() )
2683 return false;
2684
2685 if ( !authDbQuery( &query ) )
2686 return false;
2687
2688 if ( !authDbCommit() )
2689 return false;
2690
2691 QgsDebugMsgLevel( QStringLiteral( "REMOVED authority for id: %1" ).arg( id ), 2 );
2692 return true;
2693}
2694
2695const QList<QSslCertificate> QgsAuthManager::systemRootCAs()
2696{
2697 return QSslConfiguration::systemCaCertificates();
2698}
2699
2700const QList<QSslCertificate> QgsAuthManager::extraFileCAs()
2701{
2702 QMutexLocker locker( mMutex.get() );
2703 QList<QSslCertificate> certs;
2704 QList<QSslCertificate> filecerts;
2705 QVariant cafileval = QgsAuthManager::instance()->authSetting( QStringLiteral( "cafile" ) );
2706 if ( QgsVariantUtils::isNull( cafileval ) )
2707 return certs;
2708
2709 QVariant allowinvalid = QgsAuthManager::instance()->authSetting( QStringLiteral( "cafileallowinvalid" ), QVariant( false ) );
2710 if ( QgsVariantUtils::isNull( allowinvalid ) )
2711 return certs;
2712
2713 QString cafile( cafileval.toString() );
2714 if ( !cafile.isEmpty() && QFile::exists( cafile ) )
2715 {
2716 filecerts = QgsAuthCertUtils::certsFromFile( cafile );
2717 }
2718 // only CAs or certs capable of signing other certs are allowed
2719 for ( const auto &cert : std::as_const( filecerts ) )
2720 {
2721 if ( !allowinvalid.toBool() && ( cert.isBlacklisted()
2722 || cert.isNull()
2723 || cert.expiryDate() <= QDateTime::currentDateTime()
2724 || cert.effectiveDate() > QDateTime::currentDateTime() ) )
2725 {
2726 continue;
2727 }
2728
2730 {
2731 certs << cert;
2732 }
2733 }
2734 return certs;
2735}
2736
2737const QList<QSslCertificate> QgsAuthManager::databaseCAs()
2738{
2739 QMutexLocker locker( mMutex.get() );
2740 QList<QSslCertificate> certs;
2741
2742 QSqlQuery query( authDatabaseConnection() );
2743 query.prepare( QStringLiteral( "SELECT id, cert FROM %1" ).arg( authDbAuthoritiesTable() ) );
2744
2745 if ( !authDbQuery( &query ) )
2746 return certs;
2747
2748 if ( query.isActive() && query.isSelect() )
2749 {
2750 while ( query.next() )
2751 {
2752 certs << QSslCertificate( query.value( 1 ).toByteArray(), QSsl::Pem );
2753 }
2754 }
2755
2756 return certs;
2757}
2758
2759const QMap<QString, QSslCertificate> QgsAuthManager::mappedDatabaseCAs()
2760{
2761 QMutexLocker locker( mMutex.get() );
2763}
2764
2766{
2767 QMutexLocker locker( mMutex.get() );
2768 mCaCertsCache.clear();
2769 // in reverse order of precedence, with regards to duplicates, so QMap inserts overwrite
2770 insertCaCertInCache( QgsAuthCertUtils::SystemRoot, systemRootCAs() );
2771 insertCaCertInCache( QgsAuthCertUtils::FromFile, extraFileCAs() );
2772 insertCaCertInCache( QgsAuthCertUtils::InDatabase, databaseCAs() );
2773
2774 bool res = !mCaCertsCache.isEmpty(); // should at least contain system root CAs
2775 if ( !res )
2776 QgsDebugMsg( QStringLiteral( "Rebuild of CA certs cache FAILED" ) );
2777 return res;
2778}
2779
2781{
2782 QMutexLocker locker( mMutex.get() );
2783 if ( cert.isNull() )
2784 {
2785 QgsDebugMsg( QStringLiteral( "Passed certificate is null" ) );
2786 return false;
2787 }
2788
2789 removeCertTrustPolicy( cert );
2790
2791 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2792
2793 if ( policy == QgsAuthCertUtils::DefaultTrust )
2794 {
2795 QgsDebugMsg( QStringLiteral( "Passed policy was default, all cert records in database were removed for id: %1" ).arg( id ) );
2796 return true;
2797 }
2798
2799 QSqlQuery query( authDatabaseConnection() );
2800 query.prepare( QStringLiteral( "INSERT INTO %1 (id, policy) "
2801 "VALUES (:id, :policy)" ).arg( authDbTrustTable() ) );
2802
2803 query.bindValue( QStringLiteral( ":id" ), id );
2804 query.bindValue( QStringLiteral( ":policy" ), static_cast< int >( policy ) );
2805
2806 if ( !authDbStartTransaction() )
2807 return false;
2808
2809 if ( !authDbQuery( &query ) )
2810 return false;
2811
2812 if ( !authDbCommit() )
2813 return false;
2814
2815 QgsDebugMsgLevel( QStringLiteral( "Store certificate trust policy SUCCESS for id: %1" ).arg( id ), 2 );
2816 return true;
2817}
2818
2820{
2821 QMutexLocker locker( mMutex.get() );
2822 if ( cert.isNull() )
2823 {
2824 QgsDebugMsg( QStringLiteral( "Passed certificate is null" ) );
2826 }
2827
2828 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2829
2830 QSqlQuery query( authDatabaseConnection() );
2831 query.prepare( QStringLiteral( "SELECT policy FROM %1 "
2832 "WHERE id = :id" ).arg( authDbTrustTable() ) );
2833
2834 query.bindValue( QStringLiteral( ":id" ), id );
2835
2836 if ( !authDbQuery( &query ) )
2838
2840 if ( query.isActive() && query.isSelect() )
2841 {
2842 if ( query.first() )
2843 {
2844 policy = static_cast< QgsAuthCertUtils::CertTrustPolicy >( query.value( 0 ).toInt() );
2845 QgsDebugMsgLevel( QStringLiteral( "Authentication cert trust policy retrieved for id: %1" ).arg( id ), 2 );
2846 }
2847 if ( query.next() )
2848 {
2849 QgsDebugMsg( QStringLiteral( "Select contains more than one cert trust policy for id: %1" ).arg( id ) );
2850 emit messageOut( tr( "Authentication database contains duplicate cert trust policies" ), authManTag(), WARNING );
2852 }
2853 }
2854 return policy;
2855}
2856
2857bool QgsAuthManager::removeCertTrustPolicies( const QList<QSslCertificate> &certs )
2858{
2859 QMutexLocker locker( mMutex.get() );
2860 if ( certs.empty() )
2861 {
2862 QgsDebugMsg( QStringLiteral( "Passed certificate list has no certs" ) );
2863 return false;
2864 }
2865
2866 for ( const auto &cert : certs )
2867 {
2868 if ( !removeCertTrustPolicy( cert ) )
2869 return false;
2870 }
2871 return true;
2872}
2873
2874bool QgsAuthManager::removeCertTrustPolicy( const QSslCertificate &cert )
2875{
2876 QMutexLocker locker( mMutex.get() );
2877 if ( cert.isNull() )
2878 {
2879 QgsDebugMsg( QStringLiteral( "Passed certificate is null" ) );
2880 return false;
2881 }
2882
2883 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2884
2885 QSqlQuery query( authDatabaseConnection() );
2886
2887 query.prepare( QStringLiteral( "DELETE FROM %1 WHERE id = :id" ).arg( authDbTrustTable() ) );
2888
2889 query.bindValue( QStringLiteral( ":id" ), id );
2890
2891 if ( !authDbStartTransaction() )
2892 return false;
2893
2894 if ( !authDbQuery( &query ) )
2895 return false;
2896
2897 if ( !authDbCommit() )
2898 return false;
2899
2900 QgsDebugMsgLevel( QStringLiteral( "REMOVED cert trust policy for id: %1" ).arg( id ), 2 );
2901
2902 return true;
2903}
2904
2906{
2907 QMutexLocker locker( mMutex.get() );
2908 if ( cert.isNull() )
2909 {
2911 }
2912
2913 QString id( QgsAuthCertUtils::shaHexForCert( cert ) );
2914 const QStringList &trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
2915 const QStringList &untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
2916
2918 if ( trustedids.contains( id ) )
2919 {
2921 }
2922 else if ( untrustedids.contains( id ) )
2923 {
2925 }
2926 return policy;
2927}
2928
2930{
2931
2932 if ( policy == QgsAuthCertUtils::DefaultTrust )
2933 {
2934 // set default trust policy to Trusted by removing setting
2935 return removeAuthSetting( QStringLiteral( "certdefaulttrust" ) );
2936 }
2937 return storeAuthSetting( QStringLiteral( "certdefaulttrust" ), static_cast< int >( policy ) );
2938}
2939
2941{
2942 QMutexLocker locker( mMutex.get() );
2943 QVariant policy( authSetting( QStringLiteral( "certdefaulttrust" ) ) );
2944 if ( QgsVariantUtils::isNull( policy ) )
2945 {
2947 }
2948 return static_cast< QgsAuthCertUtils::CertTrustPolicy >( policy.toInt() );
2949}
2950
2952{
2953 QMutexLocker locker( mMutex.get() );
2954 mCertTrustCache.clear();
2955
2956 QSqlQuery query( authDatabaseConnection() );
2957 query.prepare( QStringLiteral( "SELECT id, policy FROM %1" ).arg( authDbTrustTable() ) );
2958
2959 if ( !authDbQuery( &query ) )
2960 {
2961 QgsDebugMsg( QStringLiteral( "Rebuild of cert trust policy cache FAILED" ) );
2962 return false;
2963 }
2964
2965 if ( query.isActive() && query.isSelect() )
2966 {
2967 while ( query.next() )
2968 {
2969 QString id = query.value( 0 ).toString();
2970 QgsAuthCertUtils::CertTrustPolicy policy = static_cast< QgsAuthCertUtils::CertTrustPolicy >( query.value( 1 ).toInt() );
2971
2972 QStringList ids;
2973 if ( mCertTrustCache.contains( policy ) )
2974 {
2975 ids = mCertTrustCache.value( policy );
2976 }
2977 mCertTrustCache.insert( policy, ids << id );
2978 }
2979 }
2980
2981 QgsDebugMsgLevel( QStringLiteral( "Rebuild of cert trust policy cache SUCCEEDED" ), 2 );
2982 return true;
2983}
2984
2985const QList<QSslCertificate> QgsAuthManager::trustedCaCerts( bool includeinvalid )
2986{
2987 QMutexLocker locker( mMutex.get() );
2989 QStringList trustedids = mCertTrustCache.value( QgsAuthCertUtils::Trusted );
2990 QStringList untrustedids = mCertTrustCache.value( QgsAuthCertUtils::Untrusted );
2991 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
2992
2993 QList<QSslCertificate> trustedcerts;
2994 for ( int i = 0; i < certpairs.size(); ++i )
2995 {
2996 QSslCertificate cert( certpairs.at( i ).second );
2997 QString certid( QgsAuthCertUtils::shaHexForCert( cert ) );
2998 if ( trustedids.contains( certid ) )
2999 {
3000 // trusted certs are always added regardless of their validity
3001 trustedcerts.append( cert );
3002 }
3003 else if ( defaultpolicy == QgsAuthCertUtils::Trusted && !untrustedids.contains( certid ) )
3004 {
3005 if ( !includeinvalid && !QgsAuthCertUtils::certIsViable( cert ) )
3006 continue;
3007 trustedcerts.append( cert );
3008 }
3009 }
3010
3011 // update application default SSL config for new requests
3012 QSslConfiguration sslconfig( QSslConfiguration::defaultConfiguration() );
3013 sslconfig.setCaCertificates( trustedcerts );
3014 QSslConfiguration::setDefaultConfiguration( sslconfig );
3015
3016 return trustedcerts;
3017}
3018
3019const QList<QSslCertificate> QgsAuthManager::untrustedCaCerts( QList<QSslCertificate> trustedCAs )
3020{
3021 QMutexLocker locker( mMutex.get() );
3022 if ( trustedCAs.isEmpty() )
3023 {
3024 if ( mTrustedCaCertsCache.isEmpty() )
3025 {
3027 }
3028 trustedCAs = trustedCaCertsCache();
3029 }
3030
3031 const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
3032
3033 QList<QSslCertificate> untrustedCAs;
3034 for ( int i = 0; i < certpairs.size(); ++i )
3035 {
3036 QSslCertificate cert( certpairs.at( i ).second );
3037 if ( !trustedCAs.contains( cert ) )
3038 {
3039 untrustedCAs.append( cert );
3040 }
3041 }
3042 return untrustedCAs;
3043}
3044
3046{
3047 QMutexLocker locker( mMutex.get() );
3048 mTrustedCaCertsCache = trustedCaCerts();
3049 QgsDebugMsgLevel( QStringLiteral( "Rebuilt trusted cert authorities cache" ), 2 );
3050 // TODO: add some error trapping for the operation
3051 return true;
3052}
3053
3055{
3056 QMutexLocker locker( mMutex.get() );
3058}
3059
3061{
3062 QMutexLocker locker( mMutex.get() );
3063 if ( masterPasswordIsSet() )
3064 {
3065 return passwordHelperWrite( mMasterPass );
3066 }
3067 return false;
3068}
3069
3070
3072
3073#endif
3074
3076{
3077 if ( isDisabled() )
3078 return;
3079
3080 const QStringList ids = configIds();
3081 for ( const auto &authcfg : ids )
3082 {
3083 clearCachedConfig( authcfg );
3084 }
3085}
3086
3087void QgsAuthManager::clearCachedConfig( const QString &authcfg )
3088{
3089 if ( isDisabled() )
3090 return;
3091
3092 QgsAuthMethod *authmethod = configAuthMethod( authcfg );
3093 if ( authmethod )
3094 {
3095 authmethod->clearCachedConfig( authcfg );
3096 }
3097}
3098
3099void QgsAuthManager::writeToConsole( const QString &message,
3100 const QString &tag,
3102{
3103 Q_UNUSED( tag )
3104
3105 // only output WARNING and CRITICAL messages
3106 if ( level == QgsAuthManager::INFO )
3107 return;
3108
3109 QString msg;
3110 switch ( level )
3111 {
3113 msg += QLatin1String( "WARNING: " );
3114 break;
3116 msg += QLatin1String( "ERROR: " );
3117 break;
3118 default:
3119 break;
3120 }
3121 msg += message;
3122
3123 QTextStream out( stdout, QIODevice::WriteOnly );
3124 out << msg << Qt::endl;
3125}
3126
3127void QgsAuthManager::tryToStartDbErase()
3128{
3129 ++mScheduledDbEraseRequestCount;
3130 // wait a total of 90 seconds for GUI availiability or user interaction, then cancel schedule
3131 int trycutoff = 90 / ( mScheduledDbEraseRequestWait ? mScheduledDbEraseRequestWait : 3 );
3132 if ( mScheduledDbEraseRequestCount >= trycutoff )
3133 {
3135 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emitting/scheduling canceled" ), 2 );
3136 return;
3137 }
3138 else
3139 {
3140 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest attempt (%1 of %2)" )
3141 .arg( mScheduledDbEraseRequestCount ).arg( trycutoff ), 2 );
3142 }
3143
3144 if ( scheduledAuthDatabaseErase() && !mScheduledDbEraseRequestEmitted && mMutex->tryLock() )
3145 {
3146 // see note in header about this signal's use
3147 mScheduledDbEraseRequestEmitted = true;
3149
3150 mMutex->unlock();
3151
3152 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emitted" ), 2 );
3153 return;
3154 }
3155 QgsDebugMsgLevel( QStringLiteral( "authDatabaseEraseRequest emit skipped" ), 2 );
3156}
3157
3158
3160{
3161 QMutexLocker locker( mMutex.get() );
3162 QMapIterator<QThread *, QMetaObject::Connection> iterator( mConnectedThreads );
3163 while ( iterator.hasNext() )
3164 {
3165 iterator.next();
3166 QThread::disconnect( iterator.value() );
3167 }
3168 locker.unlock();
3169
3170 if ( !isDisabled() )
3171 {
3173 qDeleteAll( mAuthMethods );
3174
3175 QSqlDatabase authConn = authDatabaseConnection();
3176 if ( authConn.isValid() && authConn.isOpen() )
3177 authConn.close();
3178 }
3179 delete mScheduledDbEraseTimer;
3180 mScheduledDbEraseTimer = nullptr;
3181 QSqlDatabase::removeDatabase( QStringLiteral( "authentication.configs" ) );
3182}
3183
3184
3185QString QgsAuthManager::passwordHelperName() const
3186{
3187 return tr( "Password Helper" );
3188}
3189
3190
3191void QgsAuthManager::passwordHelperLog( const QString &msg ) const
3192{
3194 {
3195 QgsMessageLog::logMessage( msg, passwordHelperName() );
3196 }
3197}
3198
3200{
3201 passwordHelperLog( tr( "Opening %1 for DELETE…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3202 bool result;
3203 QKeychain::DeletePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3204 QgsSettings settings;
3205 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3206 job.setAutoDelete( false );
3207 job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
3208 QEventLoop loop;
3209 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3210 job.start();
3211 loop.exec();
3212 if ( job.error() )
3213 {
3214 mPasswordHelperErrorCode = job.error();
3215 mPasswordHelperErrorMessage = tr( "Delete password failed: %1." ).arg( job.errorString() );
3216 // Signals used in the tests to exit main application loop
3217 emit passwordHelperFailure();
3218 result = false;
3219 }
3220 else
3221 {
3222 // Signals used in the tests to exit main application loop
3223 emit passwordHelperSuccess();
3224 result = true;
3225 }
3226 passwordHelperProcessError();
3227 return result;
3228}
3229
3230QString QgsAuthManager::passwordHelperRead()
3231{
3232 // Retrieve it!
3233 QString password;
3234 passwordHelperLog( tr( "Opening %1 for READ…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3235 QKeychain::ReadPasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3236 QgsSettings settings;
3237 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3238 job.setAutoDelete( false );
3239 job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
3240 QEventLoop loop;
3241 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3242 job.start();
3243 loop.exec();
3244 if ( job.error() )
3245 {
3246 mPasswordHelperErrorCode = job.error();
3247 mPasswordHelperErrorMessage = tr( "Retrieving password from your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() );
3248 // Signals used in the tests to exit main application loop
3249 emit passwordHelperFailure();
3250 }
3251 else
3252 {
3253 password = job.textData();
3254 // Password is there but it is empty, treat it like if it was not found
3255 if ( password.isEmpty() )
3256 {
3257 mPasswordHelperErrorCode = QKeychain::EntryNotFound;
3258 mPasswordHelperErrorMessage = tr( "Empty password retrieved from your %1." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME );
3259 // Signals used in the tests to exit main application loop
3260 emit passwordHelperFailure();
3261 }
3262 else
3263 {
3264 // Signals used in the tests to exit main application loop
3265 emit passwordHelperSuccess();
3266 }
3267 }
3268 passwordHelperProcessError();
3269 return password;
3270}
3271
3272bool QgsAuthManager::passwordHelperWrite( const QString &password )
3273{
3274 Q_ASSERT( !password.isEmpty() );
3275 bool result;
3276 passwordHelperLog( tr( "Opening %1 for WRITE…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3277 QKeychain::WritePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3278 QgsSettings settings;
3279 job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3280 job.setAutoDelete( false );
3281 job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
3282 job.setTextData( password );
3283 QEventLoop loop;
3284 connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3285 job.start();
3286 loop.exec();
3287 if ( job.error() )
3288 {
3289 mPasswordHelperErrorCode = job.error();
3290 mPasswordHelperErrorMessage = tr( "Storing password in your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() );
3291 // Signals used in the tests to exit main application loop
3292 emit passwordHelperFailure();
3293 result = false;
3294 }
3295 else
3296 {
3297 passwordHelperClearErrors();
3298 // Signals used in the tests to exit main application loop
3299 emit passwordHelperSuccess();
3300 result = true;
3301 }
3302 passwordHelperProcessError();
3303 return result;
3304}
3305
3307{
3308 // Does the user want to store the password in the wallet?
3309 QgsSettings settings;
3310 return settings.value( QStringLiteral( "use_password_helper" ), true, QgsSettings::Section::Auth ).toBool();
3311}
3312
3314{
3315 QgsSettings settings;
3316 settings.setValue( QStringLiteral( "use_password_helper" ), enabled, QgsSettings::Section::Auth );
3317 emit messageOut( enabled ? tr( "Your %1 will be <b>used from now</b> on to store and retrieve the master password." )
3319 tr( "Your %1 will <b>not be used anymore</b> to store and retrieve the master password." )
3321}
3322
3324{
3325 // Does the user want to store the password in the wallet?
3326 QgsSettings settings;
3327 return settings.value( QStringLiteral( "password_helper_logging" ), false, QgsSettings::Section::Auth ).toBool();
3328}
3329
3331{
3332 QgsSettings settings;
3333 settings.setValue( QStringLiteral( "password_helper_logging" ), enabled, QgsSettings::Section::Auth );
3334}
3335
3336void QgsAuthManager::passwordHelperClearErrors()
3337{
3338 mPasswordHelperErrorCode = QKeychain::NoError;
3339 mPasswordHelperErrorMessage.clear();
3340}
3341
3342void QgsAuthManager::passwordHelperProcessError()
3343{
3344 if ( mPasswordHelperErrorCode == QKeychain::AccessDenied ||
3345 mPasswordHelperErrorCode == QKeychain::AccessDeniedByUser ||
3346 mPasswordHelperErrorCode == QKeychain::NoBackendAvailable ||
3347 mPasswordHelperErrorCode == QKeychain::NotImplemented )
3348 {
3349 // If the error is permanent or the user denied access to the wallet
3350 // we also want to disable the wallet system to prevent annoying
3351 // notification on each subsequent access.
3352 setPasswordHelperEnabled( false );
3353 mPasswordHelperErrorMessage = tr( "There was an error and integration with your %1 system has been disabled. "
3354 "You can re-enable it at any time through the \"Utilities\" menu "
3355 "in the Authentication pane of the options dialog. %2" )
3356 .arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage );
3357 }
3358 if ( mPasswordHelperErrorCode != QKeychain::NoError )
3359 {
3360 // We've got an error from the wallet
3361 passwordHelperLog( tr( "Error in %1: %2" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage ) );
3362 emit passwordHelperMessageOut( mPasswordHelperErrorMessage, authManTag(), CRITICAL );
3363 }
3364 passwordHelperClearErrors();
3365}
3366
3367
3368bool QgsAuthManager::masterPasswordInput()
3369{
3370 if ( isDisabled() )
3371 return false;
3372
3373 QString pass;
3374 bool storedPasswordIsValid = false;
3375 bool ok = false;
3376
3377 // Read the password from the wallet
3378 if ( passwordHelperEnabled() )
3379 {
3380 pass = passwordHelperRead();
3381 if ( ! pass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) )
3382 {
3383 // Let's check the password!
3384 if ( verifyMasterPassword( pass ) )
3385 {
3386 ok = true;
3387 storedPasswordIsValid = true;
3388 emit passwordHelperMessageOut( tr( "Master password has been successfully read from your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), INFO );
3389 }
3390 else
3391 {
3392 emit passwordHelperMessageOut( tr( "Master password stored in your %1 is not valid" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), WARNING );
3393 }
3394 }
3395 }
3396
3397 if ( ! ok )
3398 {
3399 pass.clear();
3401 }
3402
3403 if ( ok && !pass.isEmpty() && mMasterPass != pass )
3404 {
3405 mMasterPass = pass;
3406 if ( passwordHelperEnabled() && ! storedPasswordIsValid )
3407 {
3408 if ( passwordHelperWrite( pass ) )
3409 {
3410 emit passwordHelperMessageOut( tr( "Master password has been successfully written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), INFO );
3411 }
3412 else
3413 {
3414 emit passwordHelperMessageOut( tr( "Master password could not be written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), WARNING );
3415 }
3416 }
3417 return true;
3418 }
3419 return false;
3420}
3421
3422bool QgsAuthManager::masterPasswordRowsInDb( int *rows ) const
3423{
3424 if ( isDisabled() )
3425 return false;
3426
3427 QSqlQuery query( authDatabaseConnection() );
3428 query.prepare( QStringLiteral( "SELECT Count(*) FROM %1" ).arg( authDbPassTable() ) );
3429
3430 bool ok = authDbQuery( &query );
3431 if ( query.first() )
3432 {
3433 *rows = query.value( 0 ).toInt();
3434 }
3435
3436 return ok;
3437}
3438
3440{
3441 if ( isDisabled() )
3442 return false;
3443
3444 int rows = 0;
3445 if ( !masterPasswordRowsInDb( &rows ) )
3446 {
3447 const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
3448 QgsDebugMsg( err );
3449 emit messageOut( tr( err ), authManTag(), CRITICAL );
3450
3451 return false;
3452 }
3453 return ( rows == 1 );
3454}
3455
3456bool QgsAuthManager::masterPasswordCheckAgainstDb( const QString &compare ) const
3457{
3458 if ( isDisabled() )
3459 return false;
3460
3461 // first verify there is only one row in auth db (uses first found)
3462
3463 QSqlQuery query( authDatabaseConnection() );
3464 query.prepare( QStringLiteral( "SELECT salt, hash FROM %1" ).arg( authDbPassTable() ) );
3465 if ( !authDbQuery( &query ) )
3466 return false;
3467
3468 if ( !query.first() )
3469 return false;
3470
3471 QString salt = query.value( 0 ).toString();
3472 QString hash = query.value( 1 ).toString();
3473
3474 return QgsAuthCrypto::verifyPasswordKeyHash( compare.isNull() ? mMasterPass : compare, salt, hash );
3475}
3476
3477bool QgsAuthManager::masterPasswordStoreInDb() const
3478{
3479 if ( isDisabled() )
3480 return false;
3481
3482 QString salt, hash, civ;
3483 QgsAuthCrypto::passwordKeyHash( mMasterPass, &salt, &hash, &civ );
3484
3485 QSqlQuery query( authDatabaseConnection() );
3486 query.prepare( QStringLiteral( "INSERT INTO %1 (salt, hash, civ) VALUES (:salt, :hash, :civ)" ).arg( authDbPassTable() ) );
3487
3488 query.bindValue( QStringLiteral( ":salt" ), salt );
3489 query.bindValue( QStringLiteral( ":hash" ), hash );
3490 query.bindValue( QStringLiteral( ":civ" ), civ );
3491
3492 if ( !authDbStartTransaction() )
3493 return false;
3494
3495 if ( !authDbQuery( &query ) )
3496 return false;
3497
3498 if ( !authDbCommit() )
3499 return false;
3500
3501 return true;
3502}
3503
3504bool QgsAuthManager::masterPasswordClearDb()
3505{
3506 if ( isDisabled() )
3507 return false;
3508
3509 QSqlQuery query( authDatabaseConnection() );
3510 query.prepare( QStringLiteral( "DELETE FROM %1" ).arg( authDbPassTable() ) );
3511 bool res = authDbTransactionQuery( &query );
3512 if ( res )
3514 return res;
3515}
3516
3517const QString QgsAuthManager::masterPasswordCiv() const
3518{
3519 if ( isDisabled() )
3520 return QString();
3521
3522 QSqlQuery query( authDatabaseConnection() );
3523 query.prepare( QStringLiteral( "SELECT civ FROM %1" ).arg( authDbPassTable() ) );
3524 if ( !authDbQuery( &query ) )
3525 return QString();
3526
3527 if ( !query.first() )
3528 return QString();
3529
3530 return query.value( 0 ).toString();
3531}
3532
3533QStringList QgsAuthManager::configIds() const
3534{
3535 QStringList configids = QStringList();
3536
3537 if ( isDisabled() )
3538 return configids;
3539
3540 QSqlQuery query( authDatabaseConnection() );
3541 query.prepare( QStringLiteral( "SELECT id FROM %1" ).arg( authDatabaseConfigTable() ) );
3542
3543 if ( !authDbQuery( &query ) )
3544 {
3545 return configids;
3546 }
3547
3548 if ( query.isActive() )
3549 {
3550 while ( query.next() )
3551 {
3552 configids << query.value( 0 ).toString();
3553 }
3554 }
3555 return configids;
3556}
3557
3558bool QgsAuthManager::verifyPasswordCanDecryptConfigs() const
3559{
3560 if ( isDisabled() )
3561 return false;
3562
3563 // no need to check for setMasterPassword, since this is private and it will be set
3564
3565 QSqlQuery query( authDatabaseConnection() );
3566
3567 query.prepare( QStringLiteral( "SELECT id, config FROM %1" ).arg( authDatabaseConfigTable() ) );
3568
3569 if ( !authDbQuery( &query ) )
3570 return false;
3571
3572 if ( !query.isActive() || !query.isSelect() )
3573 {
3574 QgsDebugMsg( QStringLiteral( "Verify password can decrypt configs FAILED, query not active or a select operation" ) );
3575 return false;
3576 }
3577
3578 int checked = 0;
3579 while ( query.next() )
3580 {
3581 ++checked;
3582 QString configstring( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), query.value( 1 ).toString() ) );
3583 if ( configstring.isEmpty() )
3584 {
3585 QgsDebugMsg( QStringLiteral( "Verify password can decrypt configs FAILED, could not decrypt a config (id: %1)" )
3586 .arg( query.value( 0 ).toString() ) );
3587 return false;
3588 }
3589 }
3590
3591 QgsDebugMsgLevel( QStringLiteral( "Verify password can decrypt configs SUCCESS (checked %1 configs)" ).arg( checked ), 2 );
3592 return true;
3593}
3594
3595bool QgsAuthManager::reencryptAllAuthenticationConfigs( const QString &prevpass, const QString &prevciv )
3596{
3597 if ( isDisabled() )
3598 return false;
3599
3600 bool res = true;
3601 const QStringList ids = configIds();
3602 for ( const auto &configid : ids )
3603 {
3604 res = res && reencryptAuthenticationConfig( configid, prevpass, prevciv );
3605 }
3606 return res;
3607}
3608
3609bool QgsAuthManager::reencryptAuthenticationConfig( const QString &authcfg, const QString &prevpass, const QString &prevciv )
3610{
3611 if ( isDisabled() )
3612 return false;
3613
3614 // no need to check for setMasterPassword, since this is private and it will be set
3615
3616 QSqlQuery query( authDatabaseConnection() );
3617
3618 query.prepare( QStringLiteral( "SELECT config FROM %1 "
3619 "WHERE id = :id" ).arg( authDatabaseConfigTable() ) );
3620
3621 query.bindValue( QStringLiteral( ":id" ), authcfg );
3622
3623 if ( !authDbQuery( &query ) )
3624 return false;
3625
3626 if ( !query.isActive() || !query.isSelect() )
3627 {
3628 QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, query not active or a select operation for authcfg: %2" ).arg( authcfg ) );
3629 return false;
3630 }
3631
3632 if ( query.first() )
3633 {
3634 QString configstring( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
3635
3636 if ( query.next() )
3637 {
3638 QgsDebugMsg( QStringLiteral( "Select contains more than one for authcfg: %1" ).arg( authcfg ) );
3639 emit messageOut( tr( "Authentication database contains duplicate configuration IDs" ), authManTag(), WARNING );
3640 return false;
3641 }
3642
3643 query.clear();
3644
3645 query.prepare( QStringLiteral( "UPDATE %1 "
3646 "SET config = :config "
3647 "WHERE id = :id" ).arg( authDatabaseConfigTable() ) );
3648
3649 query.bindValue( QStringLiteral( ":id" ), authcfg );
3650 query.bindValue( QStringLiteral( ":config" ), QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring ) );
3651
3652 if ( !authDbStartTransaction() )
3653 return false;
3654
3655 if ( !authDbQuery( &query ) )
3656 return false;
3657
3658 if ( !authDbCommit() )
3659 return false;
3660
3661 QgsDebugMsgLevel( QStringLiteral( "Reencrypt SUCCESS for authcfg: %2" ).arg( authcfg ), 2 );
3662 return true;
3663 }
3664 else
3665 {
3666 QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, could not find in db authcfg: %2" ).arg( authcfg ) );
3667 return false;
3668 }
3669}
3670
3671bool QgsAuthManager::reencryptAllAuthenticationSettings( const QString &prevpass, const QString &prevciv )
3672{
3673 // TODO: start remove (when function is actually used)
3674 Q_UNUSED( prevpass )
3675 Q_UNUSED( prevciv )
3676 return true;
3677 // end remove
3678
3679#if 0
3680 if ( isDisabled() )
3681 return false;
3682
3684 // When adding settings that require encryption, add to list //
3686
3687 QStringList encryptedsettings;
3688 encryptedsettings << "";
3689
3690 for ( const auto & sett, std::as_const( encryptedsettings ) )
3691 {
3692 if ( sett.isEmpty() || !existsAuthSetting( sett ) )
3693 continue;
3694
3695 // no need to check for setMasterPassword, since this is private and it will be set
3696
3697 QSqlQuery query( authDbConnection() );
3698
3699 query.prepare( QStringLiteral( "SELECT value FROM %1 "
3700 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
3701
3702 query.bindValue( ":setting", sett );
3703
3704 if ( !authDbQuery( &query ) )
3705 return false;
3706
3707 if ( !query.isActive() || !query.isSelect() )
3708 {
3709 QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, query not active or a select operation for setting: %2" ).arg( sett ) );
3710 return false;
3711 }
3712
3713 if ( query.first() )
3714 {
3715 QString settvalue( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
3716
3717 query.clear();
3718
3719 query.prepare( QStringLiteral( "UPDATE %1 "
3720 "SET value = :value "
3721 "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
3722
3723 query.bindValue( ":setting", sett );
3724 query.bindValue( ":value", QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), settvalue ) );
3725
3726 if ( !authDbStartTransaction() )
3727 return false;
3728
3729 if ( !authDbQuery( &query ) )
3730 return false;
3731
3732 if ( !authDbCommit() )
3733 return false;
3734
3735 QgsDebugMsg( QStringLiteral( "Reencrypt SUCCESS for setting: %2" ).arg( sett ) );
3736 return true;
3737 }
3738 else
3739 {
3740 QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, could not find in db setting: %2" ).arg( sett ) );
3741 return false;
3742 }
3743
3744 if ( query.next() )
3745 {
3746 QgsDebugMsg( QStringLiteral( "Select contains more than one for setting: %1" ).arg( sett ) );
3747 emit messageOut( tr( "Authentication database contains duplicate setting keys" ), authManTag(), WARNING );
3748 }
3749
3750 return false;
3751 }
3752
3753 return true;
3754#endif
3755}
3756
3757bool QgsAuthManager::reencryptAllAuthenticationIdentities( const QString &prevpass, const QString &prevciv )
3758{
3759 if ( isDisabled() )
3760 return false;
3761
3762 bool res = true;
3763 const QStringList ids = certIdentityIds();
3764 for ( const auto &identid : ids )
3765 {
3766 res = res && reencryptAuthenticationIdentity( identid, prevpass, prevciv );
3767 }
3768 return res;
3769}
3770
3771bool QgsAuthManager::reencryptAuthenticationIdentity(
3772 const QString &identid,
3773 const QString &prevpass,
3774 const QString &prevciv )
3775{
3776 if ( isDisabled() )
3777 return false;
3778
3779 // no need to check for setMasterPassword, since this is private and it will be set
3780
3781 QSqlQuery query( authDatabaseConnection() );
3782
3783 query.prepare( QStringLiteral( "SELECT key FROM %1 "
3784 "WHERE id = :id" ).arg( authDbIdentitiesTable() ) );
3785
3786 query.bindValue( QStringLiteral( ":id" ), identid );
3787
3788 if ( !authDbQuery( &query ) )
3789 return false;
3790
3791 if ( !query.isActive() || !query.isSelect() )
3792 {
3793 QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, query not active or a select operation for identity id: %2" ).arg( identid ) );
3794 return false;
3795 }
3796
3797 if ( query.first() )
3798 {
3799 QString keystring( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
3800
3801 if ( query.next() )
3802 {
3803 QgsDebugMsg( QStringLiteral( "Select contains more than one for identity id: %1" ).arg( identid ) );
3804 emit messageOut( tr( "Authentication database contains duplicate identity IDs" ), authManTag(), WARNING );
3805 return false;
3806 }
3807
3808 query.clear();
3809
3810 query.prepare( QStringLiteral( "UPDATE %1 "
3811 "SET key = :key "
3812 "WHERE id = :id" ).arg( authDbIdentitiesTable() ) );
3813
3814 query.bindValue( QStringLiteral( ":id" ), identid );
3815 query.bindValue( QStringLiteral( ":key" ), QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), keystring ) );
3816
3817 if ( !authDbStartTransaction() )
3818 return false;
3819
3820 if ( !authDbQuery( &query ) )
3821 return false;
3822
3823 if ( !authDbCommit() )
3824 return false;
3825
3826 QgsDebugMsgLevel( QStringLiteral( "Reencrypt SUCCESS for identity id: %2" ).arg( identid ), 2 );
3827 return true;
3828 }
3829 else
3830 {
3831 QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, could not find in db identity id: %2" ).arg( identid ) );
3832 return false;
3833 }
3834}
3835
3836bool QgsAuthManager::authDbOpen() const
3837{
3838 if ( isDisabled() )
3839 return false;
3840
3841 QSqlDatabase authdb = authDatabaseConnection();
3842 if ( !authdb.isOpen() )
3843 {
3844 if ( !authdb.open() )
3845 {
3846 QgsDebugMsg( QStringLiteral( "Unable to establish database connection\nDatabase: %1\nDriver error: %2\nDatabase error: %3" )
3848 authdb.lastError().driverText(),
3849 authdb.lastError().databaseText() ) );
3850 emit messageOut( tr( "Unable to establish authentication database connection" ), authManTag(), CRITICAL );
3851 return false;
3852 }
3853 }
3854 return true;
3855}
3856
3857bool QgsAuthManager::authDbQuery( QSqlQuery *query ) const
3858{
3859 if ( isDisabled() )
3860 return false;
3861
3862 query->setForwardOnly( true );
3863 if ( !query->exec() )
3864 {
3865 const char *err = QT_TR_NOOP( "Auth db query exec() FAILED" );
3866 QgsDebugMsg( err );
3867 emit messageOut( tr( err ), authManTag(), WARNING );
3868 return false;
3869 }
3870
3871 if ( query->lastError().isValid() )
3872 {
3873 QgsDebugMsg( QStringLiteral( "Auth db query FAILED: %1\nError: %2" )
3874 .arg( query->executedQuery(),
3875 query->lastError().text() ) );
3876 emit messageOut( tr( "Auth db query FAILED" ), authManTag(), WARNING );
3877 return false;
3878 }
3879
3880 return true;
3881}
3882
3883bool QgsAuthManager::authDbStartTransaction() const
3884{
3885 if ( isDisabled() )
3886 return false;
3887
3888 if ( !authDatabaseConnection().transaction() )
3889 {
3890 const char *err = QT_TR_NOOP( "Auth db FAILED to start transaction" );
3891 QgsDebugMsg( err );
3892 emit messageOut( tr( err ), authManTag(), WARNING );
3893 return false;
3894 }
3895
3896 return true;
3897}
3898
3899bool QgsAuthManager::authDbCommit() const
3900{
3901 if ( isDisabled() )
3902 return false;
3903
3904 if ( !authDatabaseConnection().commit() )
3905 {
3906 const char *err = QT_TR_NOOP( "Auth db FAILED to rollback changes" );
3907 QgsDebugMsg( err );
3908 emit messageOut( tr( err ), authManTag(), WARNING );
3909 ( void )authDatabaseConnection().rollback();
3910 return false;
3911 }
3912
3913 return true;
3914}
3915
3916bool QgsAuthManager::authDbTransactionQuery( QSqlQuery *query ) const
3917{
3918 if ( isDisabled() )
3919 return false;
3920
3921 if ( !authDatabaseConnection().transaction() )
3922 {
3923 const char *err = QT_TR_NOOP( "Auth db FAILED to start transaction" );
3924 QgsDebugMsg( err );
3925 emit messageOut( tr( err ), authManTag(), WARNING );
3926 return false;
3927 }
3928
3929 bool ok = authDbQuery( query );
3930
3931 if ( ok && !authDatabaseConnection().commit() )
3932 {
3933 const char *err = QT_TR_NOOP( "Auth db FAILED to rollback changes" );
3934 QgsDebugMsg( err );
3935 emit messageOut( tr( err ), authManTag(), WARNING );
3936 ( void )authDatabaseConnection().rollback();
3937 return false;
3938 }
3939
3940 return ok;
3941}
3942
3943void QgsAuthManager::insertCaCertInCache( QgsAuthCertUtils::CaCertSource source, const QList<QSslCertificate> &certs )
3944{
3945 for ( const auto &cert : certs )
3946 {
3947 mCaCertsCache.insert( QgsAuthCertUtils::shaHexForCert( cert ),
3948 QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate>( source, cert ) );
3949 }
3950}
3951
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.
Definition: qgsauthconfig.h:42
bool isValid(bool validateid=false) const
Whether the configuration is valid.
QString method() const
Textual key of the associated authentication method.
Definition: qgsauthconfig.h:78
const QString uri() const
A URI to auto-select a config when connecting to a resource.
Definition: qgsauthconfig.h:74
void setName(const QString &name)
Sets name of configuration.
Definition: qgsauthconfig.h:71
void setVersion(int version)
Sets version of the configuration.
Definition: qgsauthconfig.h:84
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.
Definition: qgsauthconfig.h:69
const QString id() const
Gets 'authcfg' 7-character alphanumeric ID of the config.
Definition: qgsauthconfig.h:64
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.
Definition: qgsauthconfig.h:82
void setMethod(const QString &method)
Definition: qgsauthconfig.h:79
void setUri(const QString &uri)
Definition: qgsauthconfig.h:75
void setId(const QString &id)
Sets auth config ID.
Definition: qgsauthconfig.h:66
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.
Abstract base class for authentication method plugins.
Definition: qgsauthmethod.h:39
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...
Definition: qgsauthmethod.h:93
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:62
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 QgsDebugMsg(str)
Definition: qgslogger.h:38