QGIS API Documentation  3.13.0-Master (5a3b1ced84)
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  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  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  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  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  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 
bool passwordHelperEnabled() const
Password helper enabled getter.
void setScheduledAuthDatabaseErase(bool scheduleErase)
Schedule an optional erase of authentication database, starting when mutex is lockable.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void setInstance(QgsCredentials *instance)
register instance
QSet< QString > IgnoredConnectionsSet
static const QString AUTH_PASSWORD_HELPER_DISPLAY_NAME
The display name of the password helper (platform dependent)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
void credentialsRequested(const QString &, QString *, QString *, const QString &, bool *)
Q_GLOBAL_STATIC(IgnoredConnectionsSet, sIgnoredConnectionsCache)
Temporary cache for ignored connections, to avoid GUI freezing by multiple credentials requests to th...
static QgsAuthManager * authManager()
Returns the application&#39;s authentication manager instance.
bool request(const QString &realm, QString &username, QString &password, const QString &message=QString()) override
request a password
void setPasswordHelperEnabled(bool enabled)
Password helper enabled setter.
QgsCredentialDialog(QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
QgsCredentialDialog constructor.
bool requestMasterPassword(QString &password, bool stored=false) override
request a master password
void credentialsRequestedMasterPassword(QString *, bool, bool *)
bool verifyMasterPassword(const QString &compare=QString())
Verify the supplied master password against any existing hash in authentication database.