QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
32QMutex QgsCredentialDialog::sIgnoredConnectionsCacheMutex;
33typedef QSet<QString> IgnoredConnectionsSet;
34
36Q_GLOBAL_STATIC( IgnoredConnectionsSet, sIgnoredConnectionsCache );
37
38
39static QString invalidStyle_( const QString &selector = QStringLiteral( "QLineEdit" ) )
40{
41 return QStringLiteral( "%1{color: rgb(200, 0, 0);}" ).arg( selector );
42}
43
44QgsCredentialDialog::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
117bool 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
133void 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
180bool 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
195void 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
307void 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
325void 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
343void 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