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