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