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