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