Skip to content

Commit 8ee2e79

Browse files
committed
Make network authentication request handling thread safe, avoid
races and crashes when auth requests occur in non-main thread Like the recent SSL error handling, this commit abstracts out the network authentication handling into a thread safe approach.
1 parent 6fa3bf8 commit 8ee2e79

9 files changed

+485
-102
lines changed

python/core/auto_generated/qgsnetworkaccessmanager.sip.in

+21-1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,22 @@ This signal is propagated to the main thread QgsNetworkAccessManager instance, s
268268
only to connect to the main thread's signal in order to receive notifications about requests
269269
created in any thread.
270270

271+
.. versionadded:: 3.6
272+
%End
273+
274+
void requestRequiresAuth( int requestId, const QString &realm );
275+
%Docstring
276+
Emitted when a network request prompts an authentication request.
277+
278+
The ``requestId`` argument reflects the unique ID identifying the original request which the authentication relates to.
279+
280+
This signal is propagated to the main thread QgsNetworkAccessManager instance, so it is necessary
281+
only to connect to the main thread's signal in order to receive notifications about authentication requests
282+
from any thread.
283+
284+
This signal is for debugging and logging purposes only, and cannot be used to respond to the
285+
requests. See QgsNetworkAuthenticationHandler for details on how to handle authentication requests.
286+
271287
.. versionadded:: 3.6
272288
%End
273289

@@ -276,12 +292,15 @@ created in any thread.
276292
%Docstring
277293
Emitted when a network request encounters SSL ``errors``.
278294

279-
The ``requestId`` argument reflects the unique ID identifying the original request which the progress report relates to.
295+
The ``requestId`` argument reflects the unique ID identifying the original request which the SSL error relates to.
280296

281297
This signal is propagated to the main thread QgsNetworkAccessManager instance, so it is necessary
282298
only to connect to the main thread's signal in order to receive notifications about SSL errors
283299
from any thread.
284300

301+
This signal is for debugging and logging purposes only, and cannot be used to respond to the errors.
302+
See QgsSslErrorHandler for details on how to handle SSL errors and potentially ignore them.
303+
285304
.. versionadded:: 3.6
286305
%End
287306

@@ -296,6 +315,7 @@ from any thread.
296315
void requestTimedOut( QNetworkReply * );
297316

298317

318+
299319
protected:
300320
virtual QNetworkReply *createRequest( QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0 );
301321

src/app/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ SET(QGIS_APP_SRCS
88
qgisappstylesheet.cpp
99
qgsabout.cpp
1010
qgsalignrasterdialog.cpp
11+
qgsappauthrequesthandler.cpp
1112
qgsappbrowserproviders.cpp
1213
qgsapplayertreeviewmenuprovider.cpp
1314
qgsappwindowmanager.cpp

src/app/qgisapp.cpp

+2-68
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
142142
#include "qgisplugin.h"
143143
#include "qgsabout.h"
144144
#include "qgsalignrasterdialog.h"
145+
#include "qgsappauthrequesthandler.h"
145146
#include "qgsappbrowserproviders.h"
146147
#include "qgsapplayertreeviewmenuprovider.h"
147148
#include "qgsapplication.h"
@@ -13883,85 +13884,18 @@ void QgisApp::namSetup()
1388313884
{
1388413885
QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance();
1388513886

13886-
connect( nam, &QNetworkAccessManager::authenticationRequired,
13887-
this, &QgisApp::namAuthenticationRequired );
13888-
1388913887
connect( nam, &QNetworkAccessManager::proxyAuthenticationRequired,
1389013888
this, &QgisApp::namProxyAuthenticationRequired );
1389113889

1389213890
connect( nam, qgis::overload< QgsNetworkRequestParameters >::of( &QgsNetworkAccessManager::requestTimedOut ),
1389313891
this, &QgisApp::namRequestTimedOut );
1389413892

13893+
nam->setAuthHandler( qgis::make_unique<QgsAppAuthRequestHandler>() );
1389513894
#ifndef QT_NO_SSL
1389613895
nam->setSslErrorHandler( qgis::make_unique<QgsAppSslErrorHandler>() );
1389713896
#endif
1389813897
}
1389913898

13900-
void QgisApp::namAuthenticationRequired( QNetworkReply *inReply, QAuthenticator *auth )
13901-
{
13902-
QPointer<QNetworkReply> reply( inReply );
13903-
Q_ASSERT( qApp->thread() == QThread::currentThread() );
13904-
13905-
QString username = auth->user();
13906-
QString password = auth->password();
13907-
13908-
if ( username.isEmpty() && password.isEmpty() && reply->request().hasRawHeader( "Authorization" ) )
13909-
{
13910-
QByteArray header( reply->request().rawHeader( "Authorization" ) );
13911-
if ( header.startsWith( "Basic " ) )
13912-
{
13913-
QByteArray auth( QByteArray::fromBase64( header.mid( 6 ) ) );
13914-
int pos = auth.indexOf( ':' );
13915-
if ( pos >= 0 )
13916-
{
13917-
username = auth.left( pos );
13918-
password = auth.mid( pos + 1 );
13919-
}
13920-
}
13921-
}
13922-
13923-
for ( ;; )
13924-
{
13925-
bool ok;
13926-
13927-
{
13928-
QMutexLocker lock( QgsCredentials::instance()->mutex() );
13929-
ok = QgsCredentials::instance()->get(
13930-
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
13931-
username, password,
13932-
tr( "Authentication required" ) );
13933-
}
13934-
if ( !ok )
13935-
return;
13936-
13937-
if ( reply.isNull() || reply->isFinished() )
13938-
return;
13939-
13940-
if ( auth->user() != username || ( password != auth->password() && !password.isNull() ) )
13941-
break;
13942-
13943-
// credentials didn't change - stored ones probably wrong? clear password and retry
13944-
{
13945-
QMutexLocker lock( QgsCredentials::instance()->mutex() );
13946-
QgsCredentials::instance()->put(
13947-
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
13948-
username, QString() );
13949-
}
13950-
}
13951-
13952-
// save credentials
13953-
{
13954-
QMutexLocker lock( QgsCredentials::instance()->mutex() );
13955-
QgsCredentials::instance()->put(
13956-
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
13957-
username, password
13958-
);
13959-
}
13960-
13961-
auth->setUser( username );
13962-
auth->setPassword( password );
13963-
}
13964-
1396513899
void QgisApp::namProxyAuthenticationRequired( const QNetworkProxy &proxy, QAuthenticator *auth )
1396613900
{
1396713901
QgsSettings settings;

src/app/qgisapp.h

-1
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,6 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
879879
void setAppStyleSheet( const QString &stylesheet );
880880

881881
//! request credentials for network manager
882-
void namAuthenticationRequired( QNetworkReply *reply, QAuthenticator *auth );
883882
void namProxyAuthenticationRequired( const QNetworkProxy &proxy, QAuthenticator *auth );
884883
void namRequestTimedOut( const QgsNetworkRequestParameters &request );
885884

src/app/qgsappauthrequesthandler.cpp

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/***************************************************************************
2+
qgsappauthrequesthandler.cpp
3+
---------------------------
4+
begin : January 2019
5+
copyright : (C) 2019 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsappauthrequesthandler.h"
17+
#include "qgslogger.h"
18+
#include "qgsauthcertutils.h"
19+
#include "qgsapplication.h"
20+
#include "qgsauthmanager.h"
21+
#include "qgisapp.h"
22+
#include "qgscredentials.h"
23+
#include <QAuthenticator>
24+
25+
void QgsAppAuthRequestHandler::handleAuthRequest( QNetworkReply *reply, QAuthenticator *auth )
26+
{
27+
QString username = auth->user();
28+
QString password = auth->password();
29+
30+
if ( username.isEmpty() && password.isEmpty() && reply->request().hasRawHeader( "Authorization" ) )
31+
{
32+
QByteArray header( reply->request().rawHeader( "Authorization" ) );
33+
if ( header.startsWith( "Basic " ) )
34+
{
35+
QByteArray auth( QByteArray::fromBase64( header.mid( 6 ) ) );
36+
int pos = auth.indexOf( ':' );
37+
if ( pos >= 0 )
38+
{
39+
username = auth.left( pos );
40+
password = auth.mid( pos + 1 );
41+
}
42+
}
43+
}
44+
45+
for ( ;; )
46+
{
47+
bool ok;
48+
49+
{
50+
QMutexLocker lock( QgsCredentials::instance()->mutex() );
51+
ok = QgsCredentials::instance()->get(
52+
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
53+
username, password,
54+
QObject::tr( "Authentication required" ) );
55+
}
56+
if ( !ok )
57+
return;
58+
59+
if ( auth->user() != username || ( password != auth->password() && !password.isNull() ) )
60+
break;
61+
62+
// credentials didn't change - stored ones probably wrong? clear password and retry
63+
{
64+
QMutexLocker lock( QgsCredentials::instance()->mutex() );
65+
QgsCredentials::instance()->put(
66+
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
67+
username, QString() );
68+
}
69+
}
70+
71+
// save credentials
72+
{
73+
QMutexLocker lock( QgsCredentials::instance()->mutex() );
74+
QgsCredentials::instance()->put(
75+
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
76+
username, password
77+
);
78+
}
79+
80+
auth->setUser( username );
81+
auth->setPassword( password );
82+
}

src/app/qgsappauthrequesthandler.h

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/***************************************************************************
2+
qgsappauthrequesthandler.h
3+
-------------------------
4+
begin : January 2019
5+
copyright : (C) 2019 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSAPPAUTHREQUESTHANDLER_H
16+
#define QGSAPPAUTHREQUESTHANDLER_H
17+
18+
#include "qgsnetworkaccessmanager.h"
19+
20+
class QgsAppAuthRequestHandler : public QgsNetworkAuthenticationHandler
21+
{
22+
23+
public:
24+
25+
void handleAuthRequest( QNetworkReply *reply, QAuthenticator *auth );
26+
27+
};
28+
29+
30+
#endif // QGSAPPAUTHREQUESTHANDLER_H

0 commit comments

Comments
 (0)