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