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