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