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