QGIS API Documentation  3.21.0-Master (5b68dc587e)
qgscredentialdialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscredentialdialog.cpp - description
3  -------------------
4  begin : February 2010
5  copyright : (C) 2010 by Juergen E. Fischer
6  email : jef at norbit dot de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgscredentialdialog.h"
19 
20 #include "qgsauthmanager.h"
21 #include "qgslogger.h"
22 #include "qgssettings.h"
23 #include "qgsapplication.h"
24 
25 #include <QPushButton>
26 #include <QMenu>
27 #include <QToolButton>
28 #include <QThread>
29 #include <QTimer>
30 #include <QGlobalStatic>
31 
32 QMutex QgsCredentialDialog::sIgnoredConnectionsCacheMutex;
33 typedef QSet<QString> IgnoredConnectionsSet;
34 
36 Q_GLOBAL_STATIC( IgnoredConnectionsSet, sIgnoredConnectionsCache );
37 
38 
39 static QString invalidStyle_( const QString &selector = QStringLiteral( "QLineEdit" ) )
40 {
41  return QStringLiteral( "%1{color: rgb(200, 0, 0);}" ).arg( selector );
42 }
43 
44 QgsCredentialDialog::QgsCredentialDialog( QWidget *parent, Qt::WindowFlags fl )
45  : QDialog( parent, fl )
46 
47 {
48  setupUi( this );
49  connect( leMasterPass, &QgsPasswordLineEdit::textChanged, this, &QgsCredentialDialog::leMasterPass_textChanged );
50  connect( leMasterPassVerify, &QgsPasswordLineEdit::textChanged, this, &QgsCredentialDialog::leMasterPassVerify_textChanged );
51  connect( chkbxEraseAuthDb, &QCheckBox::toggled, this, &QgsCredentialDialog::chkbxEraseAuthDb_toggled );
52  setInstance( this );
54  this, &QgsCredentialDialog::requestCredentials,
55  Qt::BlockingQueuedConnection );
57  this, &QgsCredentialDialog::requestCredentialsMasterPassword,
58  Qt::BlockingQueuedConnection );
59 
60  // Setup ignore button
61  mIgnoreButton->setToolTip( tr( "All requests for this connection will be automatically rejected" ) );
62  QMenu *menu = new QMenu( mIgnoreButton );
63  QAction *ignoreTemporarily = new QAction( tr( "Ignore for 10 Seconds" ), menu );
64  ignoreTemporarily->setToolTip( tr( "All requests for this connection will be automatically rejected for 10 seconds" ) );
65  QAction *ignoreForSession = new QAction( tr( "Ignore for Session" ), menu );
66  ignoreForSession->setToolTip( tr( "All requests for this connection will be automatically rejected for the duration of the current session" ) );
67  menu->addAction( ignoreTemporarily );
68  menu->addAction( ignoreForSession );
69  connect( ignoreTemporarily, &QAction::triggered, this, [ = ]
70  {
71  mIgnoreMode = IgnoreTemporarily;
72  mIgnoreButton->setText( ignoreTemporarily->text() );
73  mIgnoreButton->setToolTip( ignoreTemporarily->toolTip() );
74  } );
75  connect( ignoreForSession, &QAction::triggered, this, [ = ]
76  {
77  mIgnoreMode = IgnoreForSession;
78  mIgnoreButton->setText( ignoreForSession->text() );
79  mIgnoreButton->setToolTip( ignoreForSession->toolTip() );
80  } );
81  mIgnoreButton->setText( mIgnoreMode == IgnoreTemporarily ? ignoreTemporarily->text() : ignoreForSession->text() );
82  mIgnoreButton->setToolTip( mIgnoreMode == IgnoreTemporarily ? ignoreTemporarily->toolTip() : ignoreForSession->toolTip() );
83  mIgnoreButton->setMenu( menu );
84  mIgnoreButton->setMaximumHeight( mOkButton->sizeHint().height() );
85 
86  // Connect ok and cancel buttons
87  connect( mOkButton, &QPushButton::clicked, this, &QgsCredentialDialog::accept );
88  connect( mCancelButton, &QPushButton::clicked, this, &QgsCredentialDialog::reject );
89 
90  // Keep a cache of ignored connections, and ignore them for 10 seconds.
91  connect( mIgnoreButton, &QPushButton::clicked, this, [ = ]( bool )
92  {
93  const QString realm { labelRealm->text() };
94  {
95  const QMutexLocker locker( &sIgnoredConnectionsCacheMutex );
96  // Insert the realm in the cache of ignored connections
97  sIgnoredConnectionsCache->insert( realm );
98  }
99  if ( mIgnoreMode == IgnoreTemporarily )
100  {
101  QTimer::singleShot( 10000, nullptr, [ = ]()
102  {
103  QgsDebugMsgLevel( QStringLiteral( "Removing ignored connection from cache: %1" ).arg( realm ), 4 );
104  const QMutexLocker locker( &sIgnoredConnectionsCacheMutex );
105  sIgnoredConnectionsCache->remove( realm );
106  } );
107  }
108  accept( );
109  } );
110 
111  leMasterPass->setPlaceholderText( tr( "Required" ) );
112  chkbxPasswordHelperEnable->setText( tr( "Store/update the master password in your %1" )
114  leUsername->setFocus();
115 }
116 
117 bool QgsCredentialDialog::request( const QString &realm, QString &username, QString &password, const QString &message )
118 {
119  bool ok;
120  if ( qApp->thread() != QThread::currentThread() )
121  {
122  QgsDebugMsg( QStringLiteral( "emitting signal" ) );
123  emit credentialsRequested( realm, &username, &password, message, &ok );
124  QgsDebugMsg( QStringLiteral( "signal returned %1 (username=%2)" ).arg( ok ? "true" : "false", username ) );
125  }
126  else
127  {
128  requestCredentials( realm, &username, &password, message, &ok );
129  }
130  return ok;
131 }
132 
133 void QgsCredentialDialog::requestCredentials( const QString &realm, QString *username, QString *password, const QString &message, bool *ok )
134 {
135  Q_ASSERT( qApp->thread() == thread() && thread() == QThread::currentThread() );
136  QgsDebugMsgLevel( QStringLiteral( "Entering." ), 4 );
137  {
138  const QMutexLocker locker( &sIgnoredConnectionsCacheMutex );
139  if ( sIgnoredConnectionsCache->contains( realm ) )
140  {
141  QgsDebugMsg( QStringLiteral( "Skipping ignored connection: " ) + realm );
142  *ok = false;
143  return;
144  }
145  }
146  stackedWidget->setCurrentIndex( 0 );
147  mIgnoreButton->show();
148  chkbxPasswordHelperEnable->setChecked( QgsApplication::authManager()->passwordHelperEnabled() );
149  labelRealm->setText( realm );
150  leUsername->setText( *username );
151  lePassword->setText( *password );
152  labelMessage->setText( message );
153  labelMessage->setHidden( message.isEmpty() );
154 
155  if ( leUsername->text().isEmpty() )
156  leUsername->setFocus();
157  else
158  lePassword->setFocus();
159 
160  QWidget *activeWindow = qApp->activeWindow();
161 
162  QApplication::setOverrideCursor( Qt::ArrowCursor );
163 
164  QgsDebugMsgLevel( QStringLiteral( "exec()" ), 4 );
165  *ok = exec() == QDialog::Accepted;
166  QgsDebugMsgLevel( QStringLiteral( "exec(): %1" ).arg( *ok ? "true" : "false" ), 4 );
167 
168  QApplication::restoreOverrideCursor();
169 
170  if ( activeWindow )
171  activeWindow->raise();
172 
173  if ( *ok )
174  {
175  *username = leUsername->text();
176  *password = lePassword->text();
177  }
178 }
179 
180 bool QgsCredentialDialog::requestMasterPassword( QString &password, bool stored )
181 {
182  bool ok;
183  if ( qApp->thread() != QThread::currentThread() )
184  {
185  QgsDebugMsgLevel( QStringLiteral( "emitting signal" ), 4 );
186  emit credentialsRequestedMasterPassword( &password, stored, &ok );
187  }
188  else
189  {
190  requestCredentialsMasterPassword( &password, stored, &ok );
191  }
192  return ok;
193 }
194 
195 void QgsCredentialDialog::requestCredentialsMasterPassword( QString *password, bool stored, bool *ok )
196 {
197  QgsDebugMsgLevel( QStringLiteral( "Entering." ), 4 );
198  stackedWidget->setCurrentIndex( 1 );
199 
200  mIgnoreButton->hide();
201  leMasterPass->setFocus();
202 
203  const QString titletxt( stored ? tr( "Enter CURRENT master authentication password" ) : tr( "Set NEW master authentication password" ) );
204  lblPasswordTitle->setText( titletxt );
205 
206  chkbxPasswordHelperEnable->setChecked( QgsApplication::authManager()->passwordHelperEnabled() );
207 
208  leMasterPassVerify->setVisible( !stored );
209  lblDontForget->setVisible( !stored );
210 
211  QApplication::setOverrideCursor( Qt::ArrowCursor );
212 
213  grpbxPassAttempts->setVisible( false );
214  int passfailed = 0;
215  while ( true )
216  {
217  mOkButton->setEnabled( false );
218  // TODO: have the number of attempted passwords configurable in auth settings?
219  if ( passfailed >= 3 )
220  {
221  lblSavedForSession->setVisible( false );
222  grpbxPassAttempts->setTitle( tr( "Password attempts: %1" ).arg( passfailed ) );
223  grpbxPassAttempts->setVisible( true );
224  }
225 
226  // resize vertically to fit contents
227  QSize s = sizeHint();
228  s.setWidth( width() );
229  resize( s );
230 
231  QgsDebugMsgLevel( QStringLiteral( "exec()" ), 4 );
232  *ok = exec() == QDialog::Accepted;
233  QgsDebugMsgLevel( QStringLiteral( "exec(): %1" ).arg( *ok ? "true" : "false" ), 4 );
234 
235  if ( *ok )
236  {
237  bool passok = !leMasterPass->text().isEmpty();
238  if ( passok && stored && !chkbxEraseAuthDb->isChecked() )
239  {
240  passok = QgsApplication::authManager()->verifyMasterPassword( leMasterPass->text() );
241  }
242 
243  if ( passok && !stored )
244  {
245  passok = ( leMasterPass->text() == leMasterPassVerify->text() );
246  }
247 
248  if ( passok || chkbxEraseAuthDb->isChecked() )
249  {
250  if ( stored && chkbxEraseAuthDb->isChecked() )
251  {
253  }
254  else
255  {
256  *password = leMasterPass->text();
257  // Let's store user's preferences to use the password helper
258  if ( chkbxPasswordHelperEnable->isChecked() != QgsApplication::authManager()->passwordHelperEnabled() )
259  {
260  QgsApplication::authManager()->setPasswordHelperEnabled( chkbxPasswordHelperEnable->isChecked() );
261  }
262  }
263  break;
264  }
265  else
266  {
267  if ( stored )
268  ++passfailed;
269 
270  leMasterPass->setStyleSheet( invalidStyle_() );
271  if ( leMasterPassVerify->isVisible() )
272  {
273  leMasterPassVerify->setStyleSheet( invalidStyle_() );
274  }
275  }
276  }
277  else
278  {
279  break;
280  }
281 
282  if ( passfailed >= 5 )
283  {
284  break;
285  }
286  }
287 
288  // don't leave master password in singleton's text field, or the ability to show it
289  leMasterPass->clear();
290  leMasterPassVerify->clear();
291 
292  chkbxEraseAuthDb->setChecked( false );
293  lblSavedForSession->setVisible( true );
294 
295  // re-enable OK button or non-master-password requests will be blocked
296  // needs to come after leMasterPass->clear() or textChanged auto-slot with disable it again
297  mOkButton->setEnabled( true );
298 
299  QApplication::restoreOverrideCursor();
300 
301  if ( passfailed >= 5 )
302  {
303  close();
304  }
305 }
306 
307 void QgsCredentialDialog::leMasterPass_textChanged( const QString &pass )
308 {
309  leMasterPass->setStyleSheet( QString() );
310  bool passok = !pass.isEmpty(); // regardless of new or comparing existing, empty password disallowed
311  if ( leMasterPassVerify->isVisible() )
312  {
313  leMasterPassVerify->setStyleSheet( QString() );
314  passok = passok && ( leMasterPass->text() == leMasterPassVerify->text() );
315  }
316  mOkButton->setEnabled( passok );
317 
318  if ( leMasterPassVerify->isVisible() && !passok )
319  {
320  leMasterPass->setStyleSheet( invalidStyle_() );
321  leMasterPassVerify->setStyleSheet( invalidStyle_() );
322  }
323 }
324 
325 void QgsCredentialDialog::leMasterPassVerify_textChanged( const QString &pass )
326 {
327  if ( leMasterPassVerify->isVisible() )
328  {
329  leMasterPass->setStyleSheet( QString() );
330  leMasterPassVerify->setStyleSheet( QString() );
331 
332  // empty password disallowed
333  const bool passok = !pass.isEmpty() && ( leMasterPass->text() == leMasterPassVerify->text() );
334  mOkButton->setEnabled( passok );
335  if ( !passok )
336  {
337  leMasterPass->setStyleSheet( invalidStyle_() );
338  leMasterPassVerify->setStyleSheet( invalidStyle_() );
339  }
340  }
341 }
342 
343 void QgsCredentialDialog::chkbxEraseAuthDb_toggled( bool checked )
344 {
345  if ( checked )
346  mOkButton->setEnabled( true );
347 }
348 
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
bool verifyMasterPassword(const QString &compare=QString())
Verify the supplied master password against any existing hash in authentication database.
void setPasswordHelperEnabled(bool enabled)
Password helper enabled setter.
void setScheduledAuthDatabaseErase(bool scheduleErase)
Schedule an optional erase of authentication database, starting when mutex is lockable.
bool passwordHelperEnabled() const
Password helper enabled getter.
static const QString AUTH_PASSWORD_HELPER_DISPLAY_NAME
The display name of the password helper (platform dependent)
QgsCredentialDialog(QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
QgsCredentialDialog constructor.
bool requestMasterPassword(QString &password, bool stored=false) override
request a master password
bool request(const QString &realm, QString &username, QString &password, const QString &message=QString()) override
request a password
void credentialsRequested(const QString &, QString *, QString *, const QString &, bool *)
void credentialsRequestedMasterPassword(QString *, bool, bool *)
void setInstance(QgsCredentials *instance)
register instance
Q_GLOBAL_STATIC(IgnoredConnectionsSet, sIgnoredConnectionsCache)
Temporary cache for ignored connections, to avoid GUI freezing by multiple credentials requests to th...
QSet< QString > IgnoredConnectionsSet
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38