QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgsauthmanager.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsauthmanager.cpp
3  ---------------------
4  begin : October 5, 2014
5  copyright : (C) 2014 by Boundless Spatial, Inc. USA
6  author : Larry Shaffer
7  email : lshaffer at boundlessgeo dot com
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgsauthmanager.h"
18 
19 #include <QDir>
20 #include <QEventLoop>
21 #include <QFile>
22 #include <QFileInfo>
23 #include <QMutexLocker>
24 #include <QObject>
25 #include <QSet>
26 #include <QSqlDatabase>
27 #include <QSqlError>
28 #include <QSqlQuery>
29 #include <QTextStream>
30 #include <QTime>
31 #include <QTimer>
32 #include <QVariant>
33 #include <QSqlDriver>
34 
35 #include <QtCrypto>
36 
37 #ifndef QT_NO_SSL
38 #include <QSslConfiguration>
39 #endif
40 
41 // QtKeyChain library
42 #include "keychain.h"
43 
44 // QGIS includes
45 #include "qgsapplication.h"
46 #include "qgsauthcertutils.h"
47 #include "qgsauthcrypto.h"
48 #include "qgsauthmethod.h"
49 #include "qgsauthmethodmetadata.h"
50 #include "qgsauthmethodregistry.h"
51 #include "qgscredentials.h"
52 #include "qgslogger.h"
53 #include "qgsmessagelog.h"
54 #include "qgssettings.h"
55 
56 QgsAuthManager *QgsAuthManager::sInstance = nullptr;
57 
58 const QString QgsAuthManager::AUTH_CONFIG_TABLE = QStringLiteral( "auth_configs" );
59 const QString QgsAuthManager::AUTH_PASS_TABLE = QStringLiteral( "auth_pass" );
60 const QString QgsAuthManager::AUTH_SETTINGS_TABLE = QStringLiteral( "auth_settings" );
61 const QString QgsAuthManager::AUTH_IDENTITIES_TABLE = QStringLiteral( "auth_identities" );
62 const QString QgsAuthManager::AUTH_SERVERS_TABLE = QStringLiteral( "auth_servers" );
63 const QString QgsAuthManager::AUTH_AUTHORITIES_TABLE = QStringLiteral( "auth_authorities" );
64 const QString QgsAuthManager::AUTH_TRUST_TABLE = QStringLiteral( "auth_trust" );
65 const QString QgsAuthManager::AUTH_MAN_TAG = QObject::tr( "Authentication Manager" );
66 const QString QgsAuthManager::AUTH_CFG_REGEX = QStringLiteral( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );
67 
68 
69 const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME( "QGIS-Master-Password" );
70 const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_FOLDER_NAME( "QGIS" );
71 
72 #if defined(Q_OS_MAC)
73 const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Keychain" );
74 static const QString sDescription = QObject::tr( "Master Password <-> KeyChain storage plugin. Store and retrieve your master password in your KeyChain" );
75 #elif defined(Q_OS_WIN)
76 const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Password Manager" );
77 static const QString sDescription = QObject::tr( "Master Password <-> Password Manager storage plugin. Store and retrieve your master password in your Password Manager" );
78 #elif defined(Q_OS_LINUX)
79 const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( QStringLiteral( "Wallet/KeyRing" ) );
80 static const QString sDescription = QObject::tr( "Master Password <-> Wallet/KeyRing storage plugin. Store and retrieve your master password in your Wallet/KeyRing" );
81 #else
82 const QString QgsAuthManager::AUTH_PASSWORD_HELPER_DISPLAY_NAME( "Password Manager" );
83 static const QString sDescription = QObject::tr( "Master Password <-> KeyChain storage plugin. Store and retrieve your master password in your Wallet/KeyChain/Password Manager" );
84 #endif
85 
86 
87 
89 {
90  if ( !sInstance )
91  {
92  static QMutex sMutex;
93  QMutexLocker locker( &sMutex );
94  if ( !sInstance )
95  {
96  sInstance = new QgsAuthManager( );
97  }
98  }
99  return sInstance;
100 }
101 
102 
104 {
105  mMutex = new QMutex( QMutex::Recursive );
106  connect( this, &QgsAuthManager::messageOut,
107  this, &QgsAuthManager::writeToConsole );
108 }
109 
111 {
112  QSqlDatabase authdb;
113  if ( isDisabled() )
114  return authdb;
115 
116  // while everything we use from QSqlDatabase here is thread safe, we need to ensure
117  // that the connection cleanup on thread finalization happens in a predictable order
118  QMutexLocker locker( mMutex );
119 
120  // Sharing the same connection between threads is not allowed.
121  // We use a dedicated connection for each thread requiring access to the database,
122  // using the thread address as connection name.
123  const QString connectionName = QStringLiteral( "authentication.configs:0x%1" ).arg( reinterpret_cast<quintptr>( QThread::currentThread() ), 2 * QT_POINTER_SIZE, 16, QLatin1Char( '0' ) );
124  QgsDebugMsgLevel( QStringLiteral( "Using auth db connection name: %1 " ).arg( connectionName ), 2 );
125  if ( !QSqlDatabase::contains( connectionName ) )
126  {
127  QgsDebugMsgLevel( QStringLiteral( "No existing connection, creating a new one" ), 2 );
128  authdb = QSqlDatabase::addDatabase( QStringLiteral( "QSQLITE" ), connectionName );
129  authdb.setDatabaseName( authenticationDatabasePath() );
130  // for background threads, remove database when current thread finishes
131  if ( QThread::currentThread() != QgsApplication::instance()->thread() )
132  {
133  QgsDebugMsgLevel( QStringLiteral( "Scheduled auth db remove on thread close" ), 2 );
134 
135  // IMPORTANT - we use a direct connection here, because the database removal must happen immediately
136  // when the thread finishes, and we cannot let this get queued on the main thread's event loop (where
137  // QgsAuthManager lives).
138  // Otherwise, the QSqlDatabase's private data's thread gets reset immediately the QThread::finished,
139  // and a subsequent call to QSqlDatabase::database with the same thread address (yep it happens, actually a lot)
140  // triggers a condition in QSqlDatabase which detects the nullptr private thread data and returns an invalid database instead.
141  // QSqlDatabase::removeDatabase is thread safe, so this is ok to do.
142  // Right about now is a good time to re-evaluate your selected career ;)
143  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 = qgis::make_unique<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 = QStringLiteral( "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 = QStringLiteral( "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 = QStringLiteral( "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 = QStringLiteral( "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 = QStringLiteral( "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 = QStringLiteral( "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 = QStringLiteral( "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, &QEventLoop::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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "SELECT id, name, uri, type, version, config FROM %1 "
1218  "WHERE id = :id" ).arg( authDatabaseConfigTable() ) );
1219  }
1220  else
1221  {
1222  query.prepare( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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  QSqlDatabase::removeDatabase( QStringLiteral( "authentication.configs" ) );
3029 }
3030 
3031 
3032 QString QgsAuthManager::passwordHelperName() const
3033 {
3034  return tr( "Password Helper" );
3035 }
3036 
3037 
3038 void QgsAuthManager::passwordHelperLog( const QString &msg ) const
3039 {
3041  {
3042  QgsMessageLog::logMessage( msg, passwordHelperName() );
3043  }
3044 }
3045 
3047 {
3048  passwordHelperLog( tr( "Opening %1 for DELETE…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3049  bool result;
3050  QKeychain::DeletePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3051  QgsSettings settings;
3052  job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3053  job.setAutoDelete( false );
3054  job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
3055  QEventLoop loop;
3056  connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3057  job.start();
3058  loop.exec();
3059  if ( job.error() )
3060  {
3061  mPasswordHelperErrorCode = job.error();
3062  mPasswordHelperErrorMessage = tr( "Delete password failed: %1." ).arg( job.errorString() );
3063  // Signals used in the tests to exit main application loop
3064  emit passwordHelperFailure();
3065  result = false;
3066  }
3067  else
3068  {
3069  // Signals used in the tests to exit main application loop
3070  emit passwordHelperSuccess();
3071  result = true;
3072  }
3073  passwordHelperProcessError();
3074  return result;
3075 }
3076 
3077 QString QgsAuthManager::passwordHelperRead()
3078 {
3079  // Retrieve it!
3080  QString password;
3081  passwordHelperLog( tr( "Opening %1 for READ…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3082  QKeychain::ReadPasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3083  QgsSettings settings;
3084  job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3085  job.setAutoDelete( false );
3086  job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
3087  QEventLoop loop;
3088  connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3089  job.start();
3090  loop.exec();
3091  if ( job.error() )
3092  {
3093  mPasswordHelperErrorCode = job.error();
3094  mPasswordHelperErrorMessage = tr( "Retrieving password from your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() );
3095  // Signals used in the tests to exit main application loop
3096  emit passwordHelperFailure();
3097  }
3098  else
3099  {
3100  password = job.textData();
3101  // Password is there but it is empty, treat it like if it was not found
3102  if ( password.isEmpty() )
3103  {
3104  mPasswordHelperErrorCode = QKeychain::EntryNotFound;
3105  mPasswordHelperErrorMessage = tr( "Empty password retrieved from your %1." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME );
3106  // Signals used in the tests to exit main application loop
3107  emit passwordHelperFailure();
3108  }
3109  else
3110  {
3111  // Signals used in the tests to exit main application loop
3112  emit passwordHelperSuccess();
3113  }
3114  }
3115  passwordHelperProcessError();
3116  return password;
3117 }
3118 
3119 bool QgsAuthManager::passwordHelperWrite( const QString &password )
3120 {
3121  Q_ASSERT( !password.isEmpty() );
3122  bool result;
3123  passwordHelperLog( tr( "Opening %1 for WRITE…" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ) );
3124  QKeychain::WritePasswordJob job( AUTH_PASSWORD_HELPER_FOLDER_NAME );
3125  QgsSettings settings;
3126  job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
3127  job.setAutoDelete( false );
3128  job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
3129  job.setTextData( password );
3130  QEventLoop loop;
3131  connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
3132  job.start();
3133  loop.exec();
3134  if ( job.error() )
3135  {
3136  mPasswordHelperErrorCode = job.error();
3137  mPasswordHelperErrorMessage = tr( "Storing password in your %1 failed: %2." ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, job.errorString() );
3138  // Signals used in the tests to exit main application loop
3139  emit passwordHelperFailure();
3140  result = false;
3141  }
3142  else
3143  {
3144  passwordHelperClearErrors();
3145  // Signals used in the tests to exit main application loop
3146  emit passwordHelperSuccess();
3147  result = true;
3148  }
3149  passwordHelperProcessError();
3150  return result;
3151 }
3152 
3154 {
3155  // Does the user want to store the password in the wallet?
3156  QgsSettings settings;
3157  return settings.value( QStringLiteral( "use_password_helper" ), true, QgsSettings::Section::Auth ).toBool();
3158 }
3159 
3161 {
3162  QgsSettings settings;
3163  settings.setValue( QStringLiteral( "use_password_helper" ), enabled, QgsSettings::Section::Auth );
3164  emit messageOut( enabled ? tr( "Your %1 will be <b>used from now</b> on to store and retrieve the master password." )
3166  tr( "Your %1 will <b>not be used anymore</b> to store and retrieve the master password." )
3168 }
3169 
3171 {
3172  // Does the user want to store the password in the wallet?
3173  QgsSettings settings;
3174  return settings.value( QStringLiteral( "password_helper_logging" ), false, QgsSettings::Section::Auth ).toBool();
3175 }
3176 
3178 {
3179  QgsSettings settings;
3180  settings.setValue( QStringLiteral( "password_helper_logging" ), enabled, QgsSettings::Section::Auth );
3181 }
3182 
3183 void QgsAuthManager::passwordHelperClearErrors()
3184 {
3185  mPasswordHelperErrorCode = QKeychain::NoError;
3186  mPasswordHelperErrorMessage.clear();
3187 }
3188 
3189 void QgsAuthManager::passwordHelperProcessError()
3190 {
3191  if ( mPasswordHelperErrorCode == QKeychain::AccessDenied ||
3192  mPasswordHelperErrorCode == QKeychain::AccessDeniedByUser ||
3193  mPasswordHelperErrorCode == QKeychain::NoBackendAvailable ||
3194  mPasswordHelperErrorCode == QKeychain::NotImplemented )
3195  {
3196  // If the error is permanent or the user denied access to the wallet
3197  // we also want to disable the wallet system to prevent annoying
3198  // notification on each subsequent access.
3199  setPasswordHelperEnabled( false );
3200  mPasswordHelperErrorMessage = tr( "There was an error and integration with your %1 system has been disabled. "
3201  "You can re-enable it at any time through the \"Utilities\" menu "
3202  "in the Authentication pane of the options dialog. %2" )
3203  .arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage );
3204  }
3205  if ( mPasswordHelperErrorCode != QKeychain::NoError )
3206  {
3207  // We've got an error from the wallet
3208  passwordHelperLog( tr( "Error in %1: %2" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME, mPasswordHelperErrorMessage ) );
3209  emit passwordHelperMessageOut( mPasswordHelperErrorMessage, authManTag(), CRITICAL );
3210  }
3211  passwordHelperClearErrors();
3212 }
3213 
3214 
3215 bool QgsAuthManager::masterPasswordInput()
3216 {
3217  if ( isDisabled() )
3218  return false;
3219 
3220  QString pass;
3221  bool storedPasswordIsValid = false;
3222  bool ok = false;
3223 
3224  // Read the password from the wallet
3225  if ( passwordHelperEnabled() )
3226  {
3227  pass = passwordHelperRead();
3228  if ( ! pass.isEmpty() && ( mPasswordHelperErrorCode == QKeychain::NoError ) )
3229  {
3230  // Let's check the password!
3231  if ( verifyMasterPassword( pass ) )
3232  {
3233  ok = true;
3234  storedPasswordIsValid = true;
3235  emit passwordHelperMessageOut( tr( "Master password has been successfully read from your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), INFO );
3236  }
3237  else
3238  {
3239  emit passwordHelperMessageOut( tr( "Master password stored in your %1 is not valid" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), WARNING );
3240  }
3241  }
3242  }
3243 
3244  if ( ! ok )
3245  {
3246  pass.clear();
3248  }
3249 
3250  if ( ok && !pass.isEmpty() && mMasterPass != pass )
3251  {
3252  mMasterPass = pass;
3253  if ( passwordHelperEnabled() && ! storedPasswordIsValid )
3254  {
3255  if ( passwordHelperWrite( pass ) )
3256  {
3257  emit passwordHelperMessageOut( tr( "Master password has been successfully written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), INFO );
3258  }
3259  else
3260  {
3261  emit passwordHelperMessageOut( tr( "Master password could not be written to your %1" ).arg( AUTH_PASSWORD_HELPER_DISPLAY_NAME ), authManTag(), WARNING );
3262  }
3263  }
3264  return true;
3265  }
3266  return false;
3267 }
3268 
3269 bool QgsAuthManager::masterPasswordRowsInDb( int *rows ) const
3270 {
3271  if ( isDisabled() )
3272  return false;
3273 
3274  QSqlQuery query( authDatabaseConnection() );
3275  query.prepare( QStringLiteral( "SELECT Count(*) FROM %1" ).arg( authDbPassTable() ) );
3276 
3277  bool ok = authDbQuery( &query );
3278  if ( query.first() )
3279  {
3280  *rows = query.value( 0 ).toInt();
3281  }
3282 
3283  return ok;
3284 }
3285 
3287 {
3288  if ( isDisabled() )
3289  return false;
3290 
3291  int rows = 0;
3292  if ( !masterPasswordRowsInDb( &rows ) )
3293  {
3294  const char *err = QT_TR_NOOP( "Master password: FAILED to access database" );
3295  QgsDebugMsg( err );
3296  emit messageOut( tr( err ), authManTag(), CRITICAL );
3297 
3298  return false;
3299  }
3300  return ( rows == 1 );
3301 }
3302 
3303 bool QgsAuthManager::masterPasswordCheckAgainstDb( const QString &compare ) const
3304 {
3305  if ( isDisabled() )
3306  return false;
3307 
3308  // first verify there is only one row in auth db (uses first found)
3309 
3310  QSqlQuery query( authDatabaseConnection() );
3311  query.prepare( QStringLiteral( "SELECT salt, hash FROM %1" ).arg( authDbPassTable() ) );
3312  if ( !authDbQuery( &query ) )
3313  return false;
3314 
3315  if ( !query.first() )
3316  return false;
3317 
3318  QString salt = query.value( 0 ).toString();
3319  QString hash = query.value( 1 ).toString();
3320 
3321  return QgsAuthCrypto::verifyPasswordKeyHash( compare.isNull() ? mMasterPass : compare, salt, hash );
3322 }
3323 
3324 bool QgsAuthManager::masterPasswordStoreInDb() const
3325 {
3326  if ( isDisabled() )
3327  return false;
3328 
3329  QString salt, hash, civ;
3330  QgsAuthCrypto::passwordKeyHash( mMasterPass, &salt, &hash, &civ );
3331 
3332  QSqlQuery query( authDatabaseConnection() );
3333  query.prepare( QStringLiteral( "INSERT INTO %1 (salt, hash, civ) VALUES (:salt, :hash, :civ)" ).arg( authDbPassTable() ) );
3334 
3335  query.bindValue( QStringLiteral( ":salt" ), salt );
3336  query.bindValue( QStringLiteral( ":hash" ), hash );
3337  query.bindValue( QStringLiteral( ":civ" ), civ );
3338 
3339  if ( !authDbStartTransaction() )
3340  return false;
3341 
3342  if ( !authDbQuery( &query ) )
3343  return false;
3344 
3345  if ( !authDbCommit() )
3346  return false;
3347 
3348  return true;
3349 }
3350 
3351 bool QgsAuthManager::masterPasswordClearDb()
3352 {
3353  if ( isDisabled() )
3354  return false;
3355 
3356  QSqlQuery query( authDatabaseConnection() );
3357  query.prepare( QStringLiteral( "DELETE FROM %1" ).arg( authDbPassTable() ) );
3358  bool res = authDbTransactionQuery( &query );
3359  if ( res )
3361  return res;
3362 }
3363 
3364 const QString QgsAuthManager::masterPasswordCiv() const
3365 {
3366  if ( isDisabled() )
3367  return QString();
3368 
3369  QSqlQuery query( authDatabaseConnection() );
3370  query.prepare( QStringLiteral( "SELECT civ FROM %1" ).arg( authDbPassTable() ) );
3371  if ( !authDbQuery( &query ) )
3372  return QString();
3373 
3374  if ( !query.first() )
3375  return QString();
3376 
3377  return query.value( 0 ).toString();
3378 }
3379 
3380 QStringList QgsAuthManager::configIds() const
3381 {
3382  QStringList configids = QStringList();
3383 
3384  if ( isDisabled() )
3385  return configids;
3386 
3387  QSqlQuery query( authDatabaseConnection() );
3388  query.prepare( QStringLiteral( "SELECT id FROM %1" ).arg( authDatabaseConfigTable() ) );
3389 
3390  if ( !authDbQuery( &query ) )
3391  {
3392  return configids;
3393  }
3394 
3395  if ( query.isActive() )
3396  {
3397  while ( query.next() )
3398  {
3399  configids << query.value( 0 ).toString();
3400  }
3401  }
3402  return configids;
3403 }
3404 
3405 bool QgsAuthManager::verifyPasswordCanDecryptConfigs() const
3406 {
3407  if ( isDisabled() )
3408  return false;
3409 
3410  // no need to check for setMasterPassword, since this is private and it will be set
3411 
3412  QSqlQuery query( authDatabaseConnection() );
3413 
3414  query.prepare( QStringLiteral( "SELECT id, config FROM %1" ).arg( authDatabaseConfigTable() ) );
3415 
3416  if ( !authDbQuery( &query ) )
3417  return false;
3418 
3419  if ( !query.isActive() || !query.isSelect() )
3420  {
3421  QgsDebugMsg( QStringLiteral( "Verify password can decrypt configs FAILED, query not active or a select operation" ) );
3422  return false;
3423  }
3424 
3425  int checked = 0;
3426  while ( query.next() )
3427  {
3428  ++checked;
3429  QString configstring( QgsAuthCrypto::decrypt( mMasterPass, masterPasswordCiv(), query.value( 1 ).toString() ) );
3430  if ( configstring.isEmpty() )
3431  {
3432  QgsDebugMsg( QStringLiteral( "Verify password can decrypt configs FAILED, could not decrypt a config (id: %1)" )
3433  .arg( query.value( 0 ).toString() ) );
3434  return false;
3435  }
3436  }
3437 
3438  QgsDebugMsg( QStringLiteral( "Verify password can decrypt configs SUCCESS (checked %1 configs)" ).arg( checked ) );
3439  return true;
3440 }
3441 
3442 bool QgsAuthManager::reencryptAllAuthenticationConfigs( const QString &prevpass, const QString &prevciv )
3443 {
3444  if ( isDisabled() )
3445  return false;
3446 
3447  bool res = true;
3448  const QStringList ids = configIds();
3449  for ( const auto &configid : ids )
3450  {
3451  res = res && reencryptAuthenticationConfig( configid, prevpass, prevciv );
3452  }
3453  return res;
3454 }
3455 
3456 bool QgsAuthManager::reencryptAuthenticationConfig( const QString &authcfg, const QString &prevpass, const QString &prevciv )
3457 {
3458  if ( isDisabled() )
3459  return false;
3460 
3461  // no need to check for setMasterPassword, since this is private and it will be set
3462 
3463  QSqlQuery query( authDatabaseConnection() );
3464 
3465  query.prepare( QStringLiteral( "SELECT config FROM %1 "
3466  "WHERE id = :id" ).arg( authDatabaseConfigTable() ) );
3467 
3468  query.bindValue( QStringLiteral( ":id" ), authcfg );
3469 
3470  if ( !authDbQuery( &query ) )
3471  return false;
3472 
3473  if ( !query.isActive() || !query.isSelect() )
3474  {
3475  QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, query not active or a select operation for authcfg: %2" ).arg( authcfg ) );
3476  return false;
3477  }
3478 
3479  if ( query.first() )
3480  {
3481  QString configstring( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
3482 
3483  if ( query.next() )
3484  {
3485  QgsDebugMsg( QStringLiteral( "Select contains more than one for authcfg: %1" ).arg( authcfg ) );
3486  emit messageOut( tr( "Authentication database contains duplicate configuration IDs" ), authManTag(), WARNING );
3487  return false;
3488  }
3489 
3490  query.clear();
3491 
3492  query.prepare( QStringLiteral( "UPDATE %1 "
3493  "SET config = :config "
3494  "WHERE id = :id" ).arg( authDatabaseConfigTable() ) );
3495 
3496  query.bindValue( QStringLiteral( ":id" ), authcfg );
3497  query.bindValue( QStringLiteral( ":config" ), QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), configstring ) );
3498 
3499  if ( !authDbStartTransaction() )
3500  return false;
3501 
3502  if ( !authDbQuery( &query ) )
3503  return false;
3504 
3505  if ( !authDbCommit() )
3506  return false;
3507 
3508  QgsDebugMsg( QStringLiteral( "Reencrypt SUCCESS for authcfg: %2" ).arg( authcfg ) );
3509  return true;
3510  }
3511  else
3512  {
3513  QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, could not find in db authcfg: %2" ).arg( authcfg ) );
3514  return false;
3515  }
3516 }
3517 
3518 bool QgsAuthManager::reencryptAllAuthenticationSettings( const QString &prevpass, const QString &prevciv )
3519 {
3520  // TODO: start remove (when function is actually used)
3521  Q_UNUSED( prevpass )
3522  Q_UNUSED( prevciv )
3523  return true;
3524  // end remove
3525 
3526 #if 0
3527  if ( isDisabled() )
3528  return false;
3529 
3531  // When adding settings that require encryption, add to list //
3533 
3534  QStringList encryptedsettings;
3535  encryptedsettings << "";
3536 
3537  for ( const auto & sett, qgis::as_const( encryptedsettings ) )
3538  {
3539  if ( sett.isEmpty() || !existsAuthSetting( sett ) )
3540  continue;
3541 
3542  // no need to check for setMasterPassword, since this is private and it will be set
3543 
3544  QSqlQuery query( authDbConnection() );
3545 
3546  query.prepare( QStringLiteral( "SELECT value FROM %1 "
3547  "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
3548 
3549  query.bindValue( ":setting", sett );
3550 
3551  if ( !authDbQuery( &query ) )
3552  return false;
3553 
3554  if ( !query.isActive() || !query.isSelect() )
3555  {
3556  QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, query not active or a select operation for setting: %2" ).arg( sett ) );
3557  return false;
3558  }
3559 
3560  if ( query.first() )
3561  {
3562  QString settvalue( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
3563 
3564  query.clear();
3565 
3566  query.prepare( QStringLiteral( "UPDATE %1 "
3567  "SET value = :value "
3568  "WHERE setting = :setting" ).arg( authDbSettingsTable() ) );
3569 
3570  query.bindValue( ":setting", sett );
3571  query.bindValue( ":value", QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), settvalue ) );
3572 
3573  if ( !authDbStartTransaction() )
3574  return false;
3575 
3576  if ( !authDbQuery( &query ) )
3577  return false;
3578 
3579  if ( !authDbCommit() )
3580  return false;
3581 
3582  QgsDebugMsg( QStringLiteral( "Reencrypt SUCCESS for setting: %2" ).arg( sett ) );
3583  return true;
3584  }
3585  else
3586  {
3587  QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, could not find in db setting: %2" ).arg( sett ) );
3588  return false;
3589  }
3590 
3591  if ( query.next() )
3592  {
3593  QgsDebugMsg( QStringLiteral( "Select contains more than one for setting: %1" ).arg( sett ) );
3594  emit messageOut( tr( "Authentication database contains duplicate setting keys" ), authManTag(), WARNING );
3595  }
3596 
3597  return false;
3598  }
3599 
3600  return true;
3601 #endif
3602 }
3603 
3604 bool QgsAuthManager::reencryptAllAuthenticationIdentities( const QString &prevpass, const QString &prevciv )
3605 {
3606  if ( isDisabled() )
3607  return false;
3608 
3609  bool res = true;
3610  const QStringList ids = certIdentityIds();
3611  for ( const auto &identid : ids )
3612  {
3613  res = res && reencryptAuthenticationIdentity( identid, prevpass, prevciv );
3614  }
3615  return res;
3616 }
3617 
3618 bool QgsAuthManager::reencryptAuthenticationIdentity(
3619  const QString &identid,
3620  const QString &prevpass,
3621  const QString &prevciv )
3622 {
3623  if ( isDisabled() )
3624  return false;
3625 
3626  // no need to check for setMasterPassword, since this is private and it will be set
3627 
3628  QSqlQuery query( authDatabaseConnection() );
3629 
3630  query.prepare( QStringLiteral( "SELECT key FROM %1 "
3631  "WHERE id = :id" ).arg( authDbIdentitiesTable() ) );
3632 
3633  query.bindValue( QStringLiteral( ":id" ), identid );
3634 
3635  if ( !authDbQuery( &query ) )
3636  return false;
3637 
3638  if ( !query.isActive() || !query.isSelect() )
3639  {
3640  QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, query not active or a select operation for identity id: %2" ).arg( identid ) );
3641  return false;
3642  }
3643 
3644  if ( query.first() )
3645  {
3646  QString keystring( QgsAuthCrypto::decrypt( prevpass, prevciv, query.value( 0 ).toString() ) );
3647 
3648  if ( query.next() )
3649  {
3650  QgsDebugMsg( QStringLiteral( "Select contains more than one for identity id: %1" ).arg( identid ) );
3651  emit messageOut( tr( "Authentication database contains duplicate identity IDs" ), authManTag(), WARNING );
3652  return false;
3653  }
3654 
3655  query.clear();
3656 
3657  query.prepare( QStringLiteral( "UPDATE %1 "
3658  "SET key = :key "
3659  "WHERE id = :id" ).arg( authDbIdentitiesTable() ) );
3660 
3661  query.bindValue( QStringLiteral( ":id" ), identid );
3662  query.bindValue( QStringLiteral( ":key" ), QgsAuthCrypto::encrypt( mMasterPass, masterPasswordCiv(), keystring ) );
3663 
3664  if ( !authDbStartTransaction() )
3665  return false;
3666 
3667  if ( !authDbQuery( &query ) )
3668  return false;
3669 
3670  if ( !authDbCommit() )
3671  return false;
3672 
3673  QgsDebugMsg( QStringLiteral( "Reencrypt SUCCESS for identity id: %2" ).arg( identid ) );
3674  return true;
3675  }
3676  else
3677  {
3678  QgsDebugMsg( QStringLiteral( "Reencrypt FAILED, could not find in db identity id: %2" ).arg( identid ) );
3679  return false;
3680  }
3681 }
3682 
3683 bool QgsAuthManager::authDbOpen() const
3684 {
3685  if ( isDisabled() )
3686  return false;
3687 
3688  QSqlDatabase authdb = authDatabaseConnection();
3689  if ( !authdb.isOpen() )
3690  {
3691  if ( !authdb.open() )
3692  {
3693  QgsDebugMsg( QStringLiteral( "Unable to establish database connection\nDatabase: %1\nDriver error: %2\nDatabase error: %3" )
3695  authdb.lastError().driverText(),
3696  authdb.lastError().databaseText() ) );
3697  emit messageOut( tr( "Unable to establish authentication database connection" ), authManTag(), CRITICAL );
3698  return false;
3699  }
3700  }
3701  return true;
3702 }
3703 
3704 bool QgsAuthManager::authDbQuery( QSqlQuery *query ) const
3705 {
3706  if ( isDisabled() )
3707  return false;
3708 
3709  query->setForwardOnly( true );
3710  if ( !query->exec() )
3711  {
3712  const char *err = QT_TR_NOOP( "Auth db query exec() FAILED" );
3713  QgsDebugMsg( err );
3714  emit messageOut( tr( err ), authManTag(), WARNING );
3715  return false;
3716  }
3717 
3718  if ( query->lastError().isValid() )
3719  {
3720  QgsDebugMsg( QStringLiteral( "Auth db query FAILED: %1\nError: %2" )
3721  .arg( query->executedQuery(),
3722  query->lastError().text() ) );
3723  emit messageOut( tr( "Auth db query FAILED" ), authManTag(), WARNING );
3724  return false;
3725  }
3726 
3727  return true;
3728 }
3729 
3730 bool QgsAuthManager::authDbStartTransaction() const
3731 {
3732  if ( isDisabled() )
3733  return false;
3734 
3735  if ( !authDatabaseConnection().transaction() )
3736  {
3737  const char *err = QT_TR_NOOP( "Auth db FAILED to start transaction" );
3738  QgsDebugMsg( err );
3739  emit messageOut( tr( err ), authManTag(), WARNING );
3740  return false;
3741  }
3742 
3743  return true;
3744 }
3745 
3746 bool QgsAuthManager::authDbCommit() const
3747 {
3748  if ( isDisabled() )
3749  return false;
3750 
3751  if ( !authDatabaseConnection().commit() )
3752  {
3753  const char *err = QT_TR_NOOP( "Auth db FAILED to rollback changes" );
3754  QgsDebugMsg( err );
3755  emit messageOut( tr( err ), authManTag(), WARNING );
3756  ( void )authDatabaseConnection().rollback();
3757  return false;
3758  }
3759 
3760  return true;
3761 }
3762 
3763 bool QgsAuthManager::authDbTransactionQuery( QSqlQuery *query ) const
3764 {
3765  if ( isDisabled() )
3766  return false;
3767 
3768  if ( !authDatabaseConnection().transaction() )
3769  {
3770  const char *err = QT_TR_NOOP( "Auth db FAILED to start transaction" );
3771  QgsDebugMsg( err );
3772  emit messageOut( tr( err ), authManTag(), WARNING );
3773  return false;
3774  }
3775 
3776  bool ok = authDbQuery( query );
3777 
3778  if ( ok && !authDatabaseConnection().commit() )
3779  {
3780  const char *err = QT_TR_NOOP( "Auth db FAILED to rollback changes" );
3781  QgsDebugMsg( err );
3782  emit messageOut( tr( err ), authManTag(), WARNING );
3783  ( void )authDatabaseConnection().rollback();
3784  return false;
3785  }
3786 
3787  return ok;
3788 }
3789 
3790 void QgsAuthManager::insertCaCertInCache( QgsAuthCertUtils::CaCertSource source, const QList<QSslCertificate> &certs )
3791 {
3792  for ( const auto &cert : certs )
3793  {
3794  mCaCertsCache.insert( QgsAuthCertUtils::shaHexForCert( cert ),
3795  QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate>( source, cert ) );
3796  }
3797 }
3798 
Singleton offering an interface to manage the authentication configuration database and to utilize co...
bool rebuildTrustedCaCertsCache()
Rebuild trusted certificate authorities cache.
bool getMasterPassword(QString &password, bool stored=false)
bool isNull() const
Whether configuration is null (missing components)
void setUri(const QString &uri)
Definition: qgsauthconfig.h:70
const QString authDatabaseConfigTable() const
Name of the authentication database table that stores configs.
void messageOut(const QString &message, const QString &tag=QgsAuthManager::AUTH_MAN_TAG, QgsAuthManager::MessageLevel level=QgsAuthManager::INFO) const
Custom logging signal to relay to console output and QgsMessageLog.
bool isValid(bool validateid=false) const
Whether the configuration is valid.
static bool verifyPasswordKeyHash(const QString &pass, const QString &salt, const QString &hash, QString *hashderived=nullptr)
Verify existing master password hash to a re-generated one.
bool passwordHelperEnabled() const
Password helper enabled getter.
void setId(const QString &id)
Sets auth config ID.
Definition: qgsauthconfig.h:61
static QgsAuthManager * instance()
Enforce singleton pattern.
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
QgsAuthMethod::Expansions supportedAuthMethodExpansions(const QString &authcfg)
Gets supported authentication method expansion(s), e.g.
void setScheduledAuthDatabaseErase(bool scheduleErase)
Schedule an optional erase of authentication database, starting when mutex is lockable.
bool storeSslCertCustomConfig(const QgsAuthConfigSslServer &config)
Store an SSL certificate custom config.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
bool existsAuthSetting(const QString &key)
Check if an authentication setting exists.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QStringList authMethodList() const
Returns list of available auth methods by their keys.
bool storeAuthSetting(const QString &key, const QVariant &value, bool encrypt=false)
Store an authentication setting (stored as string via QVariant( value ).toString() ) ...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool removeCertAuthority(const QSslCertificate &cert)
Remove a certificate authority.
bool updateNetworkProxy(QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkProxy with an authentication config.
bool masterPasswordSame(const QString &pass) const
Check whether supplied password is the same as the one already set.
static QString sslErrorEnumString(QSslError::SslError errenum)
Gets short strings describing an SSL error.
bool initSslCaches()
Initialize various SSL authentication caches.
static QList< QSslCertificate > certsFromFile(const QString &certspath)
Returns a list of concatenated certs from a PEM or DER formatted file.
void passwordHelperFailure()
Signals emitted on password helper failure, mainly used in the tests to exit main application loop...
static QMap< QString, QSslCertificate > mapDigestToCerts(const QList< QSslCertificate > &certs)
Map certificate sha1 to certificate as simple cache.
Configuration container for SSL server connection exceptions or overrides.
virtual bool updateDataSourceUriItems(QStringList &connectionItems, const QString &authcfg, const QString &dataprovider=QString())
Update data source connection items with authentication components.
void passwordHelperMessageOut(const QString &message, const QString &tag=QgsAuthManager::AUTH_MAN_TAG, QgsAuthManager::MessageLevel level=QgsAuthManager::INFO)
Custom logging signal to inform the user about master password <-> password manager interactions...
static QgsCredentials * instance()
retrieves instance
static bool certificateIsAuthorityOrIssuer(const QSslCertificate &cert)
Gets whether a certificate is an Authority or can at least sign other certificates.
const QString disabledMessage() const
Standard message for when QCA&#39;s qca-ossl plugin is missing and system is disabled.
bool removeCertTrustPolicy(const QSslCertificate &cert)
Remove a certificate authority.
const QList< QgsAuthConfigSslServer > sslCertCustomConfigs()
sslCertCustomConfigs get SSL certificate custom configs
QStringList authMethodsKeys(const QString &dataprovider=QString())
Gets keys of supported authentication methods.
QgsAuthMethod * authMethod(const QString &authMethodKey)
Gets authentication method from the config/provider cache via its key.
bool storeCertAuthority(const QSslCertificate &cert)
Store a certificate authority.
const QList< QSslCertificate > extraFileCAs()
extraFileCAs extra file-based certificate authorities
static void passwordKeyHash(const QString &pass, QString *salt, QString *hash, QString *cipheriv=nullptr)
Generate SHA256 hash for master password, with iterations and salt.
MessageLevel
Message log level (mirrors that of QgsMessageLog, so it can also output there)
bool rebuildCaCertsCache()
Rebuild certificate authority cache.
QgsAuthCertUtils::CertTrustPolicy certificateTrustPolicy(const QSslCertificate &cert)
certificateTrustPolicy get trust policy for a particular certificate cert
A registry / canonical manager of authentication methods.
const QPair< QSslCertificate, QSslKey > certIdentityBundle(const QString &id)
Gets a certificate identity bundle by id (sha hash).
const QString uniqueConfigId() const
Gets a unique generated 7-character string to assign to as config id.
static const QString AUTH_PASSWORD_HELPER_DISPLAY_NAME
The display name of the password helper (platform dependent)
bool updateAuthenticationConfig(const QgsAuthMethodConfig &config)
Update an authentication config in the database.
bool eraseAuthenticationDatabase(bool backup, QString *backuppath=nullptr)
Erase all rows from all tables in authentication database.
const QList< QSslCertificate > trustedCaCerts(bool includeinvalid=false)
trustedCaCerts get list of all trusted CA certificates
virtual bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Update a network request with authentication components.
Definition: qgsauthmethod.h:95
void clearCachedConfig(const QString &authcfg)
Clear an authentication config from its associated authentication method cache.
void masterPasswordVerified(bool verified)
Emitted when a password has been verify (or not)
static bool certIsViable(const QSslCertificate &cert)
certIsViable checks for viability errors of cert and whether it is NULL
bool setDefaultCertTrustPolicy(QgsAuthCertUtils::CertTrustPolicy policy)
Sets the default certificate trust policy preferred by user.
bool removeAllAuthenticationConfigs()
Clear all authentication configs from table in database and from provider caches. ...
bool hasConfigId(const QString &txt) const
Returns whether a string includes an authcfg ID token.
const QList< QSslCertificate > untrustedCaCerts(QList< QSslCertificate > trustedCAs=QList< QSslCertificate >())
untrustedCaCerts get list of untrusted certificate authorities
bool passwordHelperLoggingEnabled() const
Password helper logging enabled getter.
QWidget * editWidget(const QString &authMethodKey, QWidget *parent=nullptr)
Returns the auth method capabilities.
QHash< QString, QgsAuthMethodConfig > QgsAuthMethodConfigsMap
const QMap< QString, QSslCertificate > mappedDatabaseCAs()
mappedDatabaseCAs get sha1-mapped database-stored certificate authorities
QgsAuthMethodConfigsMap availableAuthMethodConfigs(const QString &dataprovider=QString())
Gets mapping of authentication config ids and their base configs (not decrypted data) ...
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QString authManTag() const
Simple text tag describing authentication system for message logs.
void setMethod(const QString &method)
Definition: qgsauthconfig.h:74
~QgsAuthManager() override
Configuration storage class for authentication method configurations.
Definition: qgsauthconfig.h:38
const QString configString() const
Configuration as a concatenated string.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
const QString authenticationDatabasePath() const
The standard authentication database file in ~/.qgis3/ or defined location.
bool storeCertIdentity(const QSslCertificate &cert, const QSslKey &key)
Store a certificate identity.
bool updateDataSourceUriItems(QStringList &connectionItems, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QgsDataSourceUri with an authentication config.
bool removeCertTrustPolicies(const QList< QSslCertificate > &certs)
Remove a group certificate authorities.
bool removeSslCertCustomConfig(const QString &id, const QString &hostport)
Remove an SSL certificate custom config.
const QStringList certIdentityBundleToPem(const QString &id)
certIdentityBundleToPem get a certificate identity bundle by id (sha hash) returned as PEM text ...
bool passwordHelperSync()
Store the password manager into the wallet.
void authDatabaseChanged()
Emitted when the authentication db is significantly changed, e.g. large record removal, erased, etc.
void loadConfigString(const QString &configstr)
Load existing extended configuration.
const QString name() const
Gets name of configuration.
Definition: qgsauthconfig.h:64
const QString sslHostPort() const
Server host:port string.
void passwordHelperSuccess()
Signals emitted on password helper success, mainly used in the tests to exit main application loop...
QStringList configIds() const
Gets list of authentication ids from database.
QWidget * authMethodEditWidget(const QString &authMethodKey, QWidget *parent)
Gets authentication method edit widget via its key.
const QgsAuthConfigSslServer sslCertCustomConfigByHost(const QString &hostport)
sslCertCustomConfigByHost get an SSL certificate custom config by hostport (host:port) ...
void authDatabaseEraseRequested()
Emitted when a user has indicated they may want to erase the authentication db.
const QList< QSslError::SslError > sslIgnoredErrorEnums() const
SSL server errors (as enum list) to ignore in connections.
void setSslHostPort(const QString &hostport)
Sets server host:port string.
void updateConfigAuthMethods()
Sync the confg/authentication method cache with what is in database.
QSqlDatabase authDatabaseConnection() const
Sets up the application instance of the authentication database connection.
bool storeCertTrustPolicy(const QSslCertificate &cert, QgsAuthCertUtils::CertTrustPolicy policy)
Store user trust value for a certificate.
int version() const
Gets version of the configuration.
Definition: qgsauthconfig.h:77
virtual void clearCachedConfig(const QString &authcfg)=0
Clear any cached configuration.
bool masterPasswordHashInDatabase() const
Verify a password hash existing in authentication database.
bool passwordHelperDelete()
Delete master password from wallet.
bool storeCertAuthorities(const QList< QSslCertificate > &certs)
Store multiple certificate authorities.
bool existsCertAuthority(const QSslCertificate &cert)
Check if a certificate authority exists.
static QString shaHexForCert(const QSslCertificate &cert, bool formatted=false)
Gets the sha1 hash for certificate.
QString configAuthMethodKey(const QString &authcfg) const
Gets key of authentication method associated with config ID.
const QList< QSslCertificate > certIdentities()
certIdentities get certificate identities
virtual bool updateNetworkProxy(QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider=QString())
Update proxy settings with authentication components.
bool updateIgnoredSslErrorsCacheFromConfig(const QgsAuthConfigSslServer &config)
Update ignored SSL error cache with possible ignored SSL errors, using server config.
QgsAuthMethod * configAuthMethod(const QString &authcfg)
Gets authentication method from the config/provider cache.
const QSslCertificate sslCertificate() const
Server certificate object.
const QSslCertificate certIdentity(const QString &id)
certIdentity get a certificate identity by id (sha hash)
Abstract base class for authentication method plugins.
Definition: qgsauthmethod.h:36
static const QString encrypt(const QString &pass, const QString &cipheriv, const QString &text)
Encrypt data using master password.
CaCertSource
Type of CA certificate source.
QVariant authSetting(const QString &key, const QVariant &defaultValue=QVariant(), bool decrypt=false)
authSetting get an authentication setting (retrieved as string and returned as QVariant( QString )) ...
bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkRequest with an authentication config.
bool removeCertIdentity(const QString &id)
Remove a certificate identity.
const QByteArray trustedCaCertsPemText()
trustedCaCertsPemText get concatenated string of all trusted CA certificates
void clearMasterPassword()
Clear supplied master password.
const QgsAuthConfigSslServer sslCertCustomConfig(const QString &id, const QString &hostport)
sslCertCustomConfig get an SSL certificate custom config by id (sha hash) and hostport (host:port) ...
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
bool updateIgnoredSslErrorsCache(const QString &shahostport, const QList< QSslError > &errors)
Update ignored SSL error cache with possible ignored SSL errors, using sha:host:port key...
bool setMasterPassword(bool verify=false)
Main call to initially set or continually check master password is set.
bool resetMasterPassword(const QString &newpass, const QString &oldpass, bool keepbackup, QString *backuppath=nullptr)
Reset the master password to a new one, then re-encrypt all previous configs in a new database file...
bool storeAuthenticationConfig(QgsAuthMethodConfig &mconfig)
Store an authentication config in the database.
const QList< QSslCertificate > trustedCaCertsCache()
trustedCaCertsCache cache of trusted certificate authorities, ready for network connections ...
void setVersion(int version)
Sets version of the configuration.
Definition: qgsauthconfig.h:79
bool loadAuthenticationConfig(const QString &authcfg, QgsAuthMethodConfig &mconfig, bool full=false)
Load an authentication config from the database into subclass.
const QSslCertificate certAuthority(const QString &id)
Gets a certificate authority by id (sha hash)
bool existsSslCertCustomConfig(const QString &id, const QString &hostport)
Check if SSL certificate custom config exists.
bool isDisabled() const
Whether QCA has the qca-ossl plugin, which a base run-time requirement.
static const QString decrypt(const QString &pass, const QString &cipheriv, const QString &text)
Decrypt data using master password.
virtual bool updateNetworkReply(QNetworkReply *reply, const QString &authcfg, const QString &dataprovider=QString())
Update a network reply with authentication components.
bool configIdUnique(const QString &id) const
Verify if provided authentication id is unique.
QString method() const
Textual key of the associated authentication method.
Definition: qgsauthconfig.h:73
bool init(const QString &pluginPath=QString(), const QString &authDatabasePath=QString())
init initialize QCA, prioritize qca-ossl plugin and optionally set up the authentication database ...
void loadConfigString(const QString &config=QString())
Load concatenated string into configuration, e.g. from auth database.
void setPasswordHelperEnabled(bool enabled)
Password helper enabled setter.
static const QString AUTH_MAN_TAG
The display name of the Authentication Manager.
const QString uri() const
A URI to auto-select a config when connecting to a resource.
Definition: qgsauthconfig.h:69
bool existsCertIdentity(const QString &id)
Check if a certificate identity exists.
bool masterPasswordIsSet() const
Whether master password has be input and verified, i.e. authentication database is accessible...
const QList< QSslCertificate > databaseCAs()
databaseCAs get database-stored certificate authorities
CertTrustPolicy
Type of certificate trust policy.
QgsAuthMethodsMap authMethodsMap(const QString &dataprovider=QString())
Gets available authentication methods mapped to their key.
bool scheduledAuthDatabaseErase()
Whether there is a scheduled opitonal erase of authentication database.
bool updateNetworkReply(QNetworkReply *reply, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkReply with an authentication config (used to skip known SSL errors...
bool removeAuthenticationConfig(const QString &authcfg)
Remove an authentication config in the database.
QgsAuthCertUtils::CertTrustPolicy certTrustPolicy(const QSslCertificate &cert)
certTrustPolicy get whether certificate cert is trusted by user
void dumpIgnoredSslErrorsCache_()
Utility function to dump the cache for debug purposes.
const QList< QSslCertificate > systemRootCAs()
systemRootCAs get root system certificate authorities
QgsAuthCertUtils::CertTrustPolicy defaultCertTrustPolicy()
Gets the default certificate trust policy preferred by user.
const QString authDatabaseServersTable() const
Name of the authentication database table that stores server exceptions/configs.
QgsAuthMethod::Expansions supportedExpansions() const
Flags that represent the update points (where authentication configurations are expanded) supported b...
Definition: qgsauthmethod.h:79
bool removeAuthSetting(const QString &key)
Remove an authentication setting.
static QgsAuthMethodRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
void setName(const QString &name)
Sets name of configuration.
Definition: qgsauthconfig.h:66
const QString configString() const
The extended configuration, as stored and retrieved from the authentication database.
bool rebuildIgnoredSslErrorCache()
Rebuild ignoredSSL error cache.
void setSslCertificate(const QSslCertificate &cert)
Sets server certificate object.
bool backupAuthenticationDatabase(QString *backuppath=nullptr)
Close connection to current authentication database and back it up.
virtual void updateMethodConfig(QgsAuthMethodConfig &mconfig)=0
Update an authentication configuration in place.
bool registerCoreAuthMethods()
Instantiate and register existing C++ core authentication methods from plugins.
QStringList certIdentityIds() const
certIdentityIds get list of certificate identity ids from database
void setPasswordHelperLoggingEnabled(bool enabled)
Password helper logging enabled setter.
const QString id() const
Gets &#39;authcfg&#39; 7-character alphanumeric ID of the config.
Definition: qgsauthconfig.h:59
static QByteArray certsToPemText(const QList< QSslCertificate > &certs)
certsToPemText dump a list of QSslCertificates to PEM text
bool verifyMasterPassword(const QString &compare=QString())
Verify the supplied master password against any existing hash in authentication database.
void clearAllCachedConfigs()
Clear all authentication configs from authentication method caches.
bool rebuildCertTrustCache()
Rebuild certificate authority cache.
QHash< QString, QgsAuthMethod * > QgsAuthMethodsMap