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