Skip to content

Commit 2af3535

Browse files
committed
[needsbackport] apply an alternative fix for #20826
Partly reverts c9e7616, which removed the synchronizatiion of credential requests (eg. in a project that has multiple layers from the same postgresql database without credentials) and led to multiple concurrent requests for the same credentials. Some of which were silently discarded, when events processed in the dialogs exec() event loop tried to reinvoke the dialog and caused invalid layers. Authentications caused by network requests can still cause this. The credential cache is now guarded by a separate mutex.
1 parent 78b5678 commit 2af3535

13 files changed

+86
-76
lines changed

python/core/auto_generated/qgscredentials.sip.in

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ credential creator function.
2020

2121
QGIS application uses QgsCredentialDialog class for displaying a dialog to the user.
2222

23+
Caller can use the mutex to synchronize authentications to avoid requesting
24+
credentials for the same resource several times.
25+
2326
Object deletes itself when it's not needed anymore. Children should use
2427
signal destroyed() to be notified of the deletion
2528
%End
@@ -68,27 +71,27 @@ combinations are used with this method.
6871
retrieves instance
6972
%End
7073

71-
void lock() /Deprecated/;
74+
void lock();
7275
%Docstring
7376
Lock the instance against access from multiple threads. This does not really lock access to get/put methds,
7477
it will just prevent other threads to lock the instance and continue the execution. When the class is used
7578
from non-GUI threads, they should call lock() before the get/put calls to avoid race conditions.
7679

77-
.. deprecated:: since QGIS 3.4 - mutex locking is automatically handled
80+
.. versionadded:: 2.4
7881
%End
7982

80-
void unlock() /Deprecated/;
83+
void unlock();
8184
%Docstring
8285
Unlock the instance after being locked.
8386

84-
.. deprecated:: since QGIS 3.4 - mutex locking is automatically handled
87+
.. versionadded:: 2.4
8588
%End
8689

87-
QMutex *mutex() /Deprecated/;
90+
QMutex *mutex();
8891
%Docstring
8992
Returns pointer to mutex
9093

91-
.. deprecated:: since QGIS 3.4 - mutex locking is automatically handled
94+
.. versionadded:: 2.4
9295
%End
9396

9497
protected:

src/app/qgisapp.cpp

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13956,35 +13956,30 @@ void QgisApp::namProxyAuthenticationRequired( const QNetworkProxy &proxy, QAuthe
1395613956

1395713957
for ( ;; )
1395813958
{
13959-
bool ok;
13960-
13961-
{
13962-
ok = QgsCredentials::instance()->get(
13963-
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
13964-
username, password,
13965-
tr( "Proxy authentication required" ) );
13966-
}
13959+
bool ok = QgsCredentials::instance()->get(
13960+
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
13961+
username, password,
13962+
tr( "Proxy authentication required" ) );
1396713963
if ( !ok )
1396813964
return;
1396913965

1397013966
if ( auth->user() != username || ( password != auth->password() && !password.isNull() ) )
13967+
{
13968+
QgsCredentials::instance()->put(
13969+
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
13970+
username, password
13971+
);
1397113972
break;
13972-
13973-
// credentials didn't change - stored ones probably wrong? clear password and retry
13973+
}
13974+
else
1397413975
{
13976+
// credentials didn't change - stored ones probably wrong? clear password and retry
1397513977
QgsCredentials::instance()->put(
1397613978
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
1397713979
username, QString() );
1397813980
}
1397913981
}
1398013982

13981-
{
13982-
QgsCredentials::instance()->put(
13983-
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
13984-
username, password
13985-
);
13986-
}
13987-
1398813983
auth->setUser( username );
1398913984
auth->setPassword( password );
1399013985
}

src/app/qgsappauthrequesthandler.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
#include "qgsauthmanager.h"
2121
#include "qgisapp.h"
2222
#include "qgscredentials.h"
23+
2324
#include <QAuthenticator>
2425

2526
void QgsAppAuthRequestHandler::handleAuthRequest( QNetworkReply *reply, QAuthenticator *auth )
2627
{
28+
Q_ASSERT( qApp->thread() == QThread::currentThread() );
29+
2730
QString username = auth->user();
2831
QString password = auth->password();
2932

@@ -52,20 +55,23 @@ void QgsAppAuthRequestHandler::handleAuthRequest( QNetworkReply *reply, QAuthent
5255
return;
5356

5457
if ( auth->user() != username || ( password != auth->password() && !password.isNull() ) )
58+
{
59+
// save credentials
60+
QgsCredentials::instance()->put(
61+
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
62+
username, password
63+
);
5564
break;
56-
57-
// credentials didn't change - stored ones probably wrong? clear password and retry
58-
QgsCredentials::instance()->put(
59-
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
60-
username, QString() );
65+
}
66+
else
67+
{
68+
// credentials didn't change - stored ones probably wrong? clear password and retry
69+
QgsCredentials::instance()->put(
70+
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
71+
username, QString() );
72+
}
6173
}
6274

63-
// save credentials
64-
QgsCredentials::instance()->put(
65-
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
66-
username, password
67-
);
68-
6975
auth->setUser( username );
7076
auth->setPassword( password );
7177
}

src/app/qgsgeometryvalidationdock.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,18 +289,14 @@ void QgsGeometryValidationDock::onCurrentLayerChanged( QgsMapLayer *layer )
289289

290290
void QgsGeometryValidationDock::onLayerEditingStatusChanged()
291291
{
292-
bool enabled = false;
293292
if ( mCurrentLayer && mCurrentLayer->isSpatial() && mCurrentLayer->isEditable() )
294293
{
295294
const QList<QgsGeometryCheckFactory *> topologyCheckFactories = QgsAnalysis::instance()->geometryCheckRegistry()->geometryCheckFactories( mCurrentLayer, QgsGeometryCheck::LayerCheck, QgsGeometryCheck::Flag::AvailableInValidation );
296295
const QStringList activeChecks = mCurrentLayer->geometryOptions()->geometryChecks();
297296
for ( const QgsGeometryCheckFactory *factory : topologyCheckFactories )
298297
{
299298
if ( activeChecks.contains( factory->id() ) )
300-
{
301-
enabled = true;
302299
break;
303-
}
304300
}
305301
}
306302
}

src/core/auth/qgsauthmanager.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3203,9 +3203,8 @@ bool QgsAuthManager::masterPasswordInput()
32033203

32043204
if ( ! ok )
32053205
{
3206-
QgsCredentials *creds = QgsCredentials::instance();
32073206
pass.clear();
3208-
ok = creds->getMasterPassword( pass, masterPasswordHashInDatabase() );
3207+
ok = QgsCredentials::instance()->getMasterPassword( pass, masterPasswordHashInDatabase() );
32093208
}
32103209

32113210
if ( ok && !pass.isEmpty() && mMasterPass != pass )

src/core/qgscredentials.cpp

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,23 @@ QgsCredentials *QgsCredentials::instance()
4040

4141
bool QgsCredentials::get( const QString &realm, QString &username, QString &password, const QString &message )
4242
{
43-
QMutexLocker locker( &mMutex );
44-
if ( mCredentialCache.contains( realm ) )
4543
{
46-
QPair<QString, QString> credentials = mCredentialCache.take( realm );
47-
locker.unlock();
48-
username = credentials.first;
49-
password = credentials.second;
50-
#if 0 // don't leak credentials on log
51-
QgsDebugMsg( QStringLiteral( "retrieved realm:%1 username:%2 password:%3" ).arg( realm, username, password ) );
52-
#endif
53-
54-
if ( !password.isNull() )
55-
return true;
44+
QMutexLocker locker( &mCacheMutex );
45+
if ( mCredentialCache.contains( realm ) )
46+
{
47+
QPair<QString, QString> credentials = mCredentialCache.take( realm );
48+
username = credentials.first;
49+
password = credentials.second;
50+
QgsDebugMsg( QStringLiteral( "retrieved realm:%1 username:%2" ).arg( realm, username ) );
51+
52+
if ( !password.isNull() )
53+
return true;
54+
}
5655
}
57-
locker.unlock();
5856

5957
if ( request( realm, username, password, message ) )
6058
{
61-
#if 0 // don't leak credentials on log
62-
QgsDebugMsg( QStringLiteral( "requested realm:%1 username:%2 password:%3" ).arg( realm, username, password ) );
63-
#endif
59+
QgsDebugMsg( QStringLiteral( "requested realm:%1 username:%2" ).arg( realm, username ) );
6460
return true;
6561
}
6662
else
@@ -72,10 +68,8 @@ bool QgsCredentials::get( const QString &realm, QString &username, QString &pass
7268

7369
void QgsCredentials::put( const QString &realm, const QString &username, const QString &password )
7470
{
75-
#if 0 // don't leak credentials on log
76-
QgsDebugMsg( QStringLiteral( "inserting realm:%1 username:%2 password:%3" ).arg( realm, username, password ) );
77-
#endif
78-
QMutexLocker locker( &mMutex );
71+
QMutexLocker locker( &mCacheMutex );
72+
QgsDebugMsg( QStringLiteral( "inserting realm:%1 username:%2" ).arg( realm, username ) );
7973
mCredentialCache.insert( realm, QPair<QString, QString>( username, password ) );
8074
}
8175

@@ -91,12 +85,12 @@ bool QgsCredentials::getMasterPassword( QString &password, bool stored )
9185

9286
void QgsCredentials::lock()
9387
{
94-
mMutex.lock();
88+
mAuthMutex.lock();
9589
}
9690

9791
void QgsCredentials::unlock()
9892
{
99-
mMutex.unlock();
93+
mAuthMutex.unlock();
10094
}
10195

10296

src/core/qgscredentials.h

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
3636
* QGIS application uses QgsCredentialDialog class for displaying a dialog to the user.
3737
38+
* Caller can use the mutex to synchronize authentications to avoid requesting
39+
* credentials for the same resource several times.
40+
3841
* Object deletes itself when it's not needed anymore. Children should use
3942
* signal destroyed() to be notified of the deletion
4043
*/
@@ -84,25 +87,21 @@ class CORE_EXPORT QgsCredentials
8487
* Lock the instance against access from multiple threads. This does not really lock access to get/put methds,
8588
* it will just prevent other threads to lock the instance and continue the execution. When the class is used
8689
* from non-GUI threads, they should call lock() before the get/put calls to avoid race conditions.
87-
*
88-
* \deprecated since QGIS 3.4 - mutex locking is automatically handled
90+
* \since QGIS 2.4
8991
*/
90-
Q_DECL_DEPRECATED void lock() SIP_DEPRECATED;
92+
void lock();
9193

9294
/**
9395
* Unlock the instance after being locked.
94-
* \deprecated since QGIS 3.4 - mutex locking is automatically handled
96+
* \since QGIS 2.4
9597
*/
96-
Q_DECL_DEPRECATED void unlock() SIP_DEPRECATED;
98+
void unlock();
9799

98100
/**
99101
* Returns pointer to mutex
100-
* \deprecated since QGIS 3.4 - mutex locking is automatically handled
102+
* \since QGIS 2.4
101103
*/
102-
Q_DECL_DEPRECATED QMutex *mutex() SIP_DEPRECATED
103-
{
104-
return &mMutex;
105-
}
104+
QMutex *mutex() { return &mAuthMutex; }
106105

107106
protected:
108107

@@ -133,7 +132,11 @@ class CORE_EXPORT QgsCredentials
133132
//! Pointer to the credential instance
134133
static QgsCredentials *sInstance;
135134

136-
QMutex mMutex;
135+
//! Mutex to synchronize authentications
136+
QMutex mAuthMutex;
137+
138+
//! Mutex to guard the cache
139+
QMutex mCacheMutex;
137140
};
138141

139142

src/core/qgsnetworkaccessmanager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ void QgsNetworkAccessManager::onAuthRequired( QNetworkReply *reply, QAuthenticat
414414
// in main thread this will trigger auth handler immediately and return once the request is satisfied,
415415
// while in worker thread the signal will be queued (and return immediately) -- hence the need to lock the thread in the next block
416416
emit authRequestOccurred( reply, auth );
417+
417418
if ( this != sMainNAM )
418419
{
419420
// lock thread and wait till error is handled. If we return from this slot now, then the reply will resume

src/gui/qgscredentialdialog.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ bool QgsCredentialDialog::request( const QString &realm, QString &username, QStr
5959
{
6060
QgsDebugMsg( QStringLiteral( "emitting signal" ) );
6161
emit credentialsRequested( realm, &username, &password, message, &ok );
62-
QgsDebugMsg( QStringLiteral( "signal returned %1 (username=%2, password=%3)" ).arg( ok ? "true" : "false", username, password ) );
62+
QgsDebugMsg( QStringLiteral( "signal returned %1 (username=%2)" ).arg( ok ? "true" : "false", username ) );
6363
}
6464
else
6565
{
@@ -81,7 +81,9 @@ void QgsCredentialDialog::requestCredentials( const QString &realm, QString *use
8181
labelMessage->setText( message );
8282
labelMessage->setHidden( message.isEmpty() );
8383

84-
if ( !leUsername->text().isEmpty() )
84+
if ( leUsername->text().isEmpty() )
85+
leUsername->setFocus();
86+
else
8587
lePassword->setFocus();
8688

8789
QWidget *activeWindow = qApp->activeWindow();

src/providers/db2/qgsdb2provider.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ QSqlDatabase QgsDb2Provider::getDatabase( const QString &connInfo, QString &errM
235235
db.setPort( port.toInt() );
236236
bool connected = false;
237237
int i = 0;
238+
QgsCredentials::instance()->lock();
238239
while ( !connected && i < 3 )
239240
{
240241
i++;
@@ -248,6 +249,7 @@ QSqlDatabase QgsDb2Provider::getDatabase( const QString &connInfo, QString &errM
248249
{
249250
errMsg = QStringLiteral( "Cancel clicked" );
250251
QgsDebugMsg( errMsg );
252+
QgsCredentials::instance()->unlock();
251253
break;
252254
}
253255
}
@@ -289,6 +291,7 @@ QSqlDatabase QgsDb2Provider::getDatabase( const QString &connInfo, QString &errM
289291
{
290292
QgsCredentials::instance()->put( databaseName, userName, password );
291293
}
294+
QgsCredentials::instance()->unlock();
292295

293296
return db;
294297
}

src/providers/grass/qgsgrass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ void QgsGrass::loadMapsetSearchPath()
700700

701701
void QgsGrass::setMapsetSearchPathWatcher()
702702
{
703-
QgsDebugMsg( "etered" );
703+
QgsDebugMsg( "entered." );
704704
if ( mMapsetSearchPathWatcher )
705705
{
706706
delete mMapsetSearchPathWatcher;

src/providers/oracle/qgsoracleconn.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ QgsOracleConn::QgsOracleConn( QgsDataSourceUri uri )
9696
QgsDebugMsg( QStringLiteral( "Connecting with options: " ) + options );
9797
if ( !mDatabase.open() )
9898
{
99+
QgsCredentials::instance()->lock();
100+
99101
while ( !mDatabase.open() )
100102
{
101103
bool ok = QgsCredentials::instance()->get( realm, username, password, mDatabase.lastError().text() );
@@ -125,6 +127,8 @@ QgsOracleConn::QgsOracleConn( QgsDataSourceUri uri )
125127

126128
if ( mDatabase.isOpen() )
127129
QgsCredentials::instance()->put( realm, username, password );
130+
131+
QgsCredentials::instance()->unlock();
128132
}
129133

130134
if ( !mDatabase.isOpen() )

src/providers/postgres/qgspostgresconn.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ QgsPostgresConn::QgsPostgresConn( const QString &conninfo, bool readOnly, bool s
272272
QString username = uri.username();
273273
QString password = uri.password();
274274

275+
QgsCredentials::instance()->lock();
276+
275277
int i = 0;
276278
while ( PQstatus() != CONNECTION_OK && i < 5 )
277279
{
@@ -296,6 +298,8 @@ QgsPostgresConn::QgsPostgresConn( const QString &conninfo, bool readOnly, bool s
296298

297299
if ( PQstatus() == CONNECTION_OK )
298300
QgsCredentials::instance()->put( conninfo, username, password );
301+
302+
QgsCredentials::instance()->unlock();
299303
}
300304

301305
if ( PQstatus() != CONNECTION_OK )

0 commit comments

Comments
 (0)