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