QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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" ).arg( QgsAuthManager::passwordHelperDisplayName() ) );
113 leUsername->setFocus();
114}
115
116bool QgsCredentialDialog::request( const QString &realm, QString &username, QString &password, const QString &message )
117{
118 bool ok;
119 if ( qApp->thread() != QThread::currentThread() )
120 {
121 QgsDebugMsgLevel( u"emitting signal"_s, 2 );
122 emit credentialsRequested( realm, &username, &password, message, &ok );
123 QgsDebugMsgLevel( u"signal returned %1 (username=%2)"_s.arg( ok ? "true" : "false", username ), 2 );
124 }
125 else
126 {
127 requestCredentials( realm, &username, &password, message, &ok );
128 }
129 return ok;
130}
131
132void QgsCredentialDialog::requestCredentials( const QString &realm, QString *username, QString *password, const QString &message, bool *ok )
133{
134 Q_ASSERT( qApp->thread() == thread() && thread() == QThread::currentThread() );
135 QgsDebugMsgLevel( u"Entering."_s, 4 );
136 {
137 const QMutexLocker locker( &sIgnoredConnectionsCacheMutex );
138 if ( sIgnoredConnectionsCache->contains( realm ) )
139 {
140 QgsDebugMsgLevel( u"Skipping ignored connection: "_s + realm, 2 );
141 *ok = false;
142 return;
143 }
144 }
145 stackedWidget->setCurrentIndex( 0 );
146 mIgnoreButton->show();
147 chkbxPasswordHelperEnable->setChecked( QgsApplication::authManager()->passwordHelperEnabled() );
148 labelRealm->setText( QgsDataSourceUri::removePassword( realm, true ) );
149 mRealm = 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( u"exec()"_s, 4 );
165 *ok = exec() == QDialog::Accepted;
166 QgsDebugMsgLevel( u"exec(): %1"_s.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( u"emitting signal"_s, 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( u"Entering."_s, 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( u"exec()"_s, 4 );
232 *ok = exec() == QDialog::Accepted;
233 QgsDebugMsgLevel( u"exec(): %1"_s.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}
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