Skip to content

Commit 471ee83

Browse files
committed
Preliminary work to support QFC connection through SSO
1 parent 9692f33 commit 471ee83

8 files changed

+349
-37
lines changed

src/core/qfieldappauthrequesthandler.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@ void QFieldAppAuthRequestHandler::enterCredentials( const QString &realm, const
3939
QgsCredentials::instance()->put( realm, username, password );
4040
}
4141

42+
bool QFieldAppAuthRequestHandler::isProjectLoading() const
43+
{
44+
return mIsProjectLoading;
45+
}
46+
47+
void QFieldAppAuthRequestHandler::setIsProjectLoading( bool loading )
48+
{
49+
if ( mIsProjectLoading == loading )
50+
return;
51+
52+
mIsProjectLoading = loading;
53+
emit isProjectLoadingChanged();
54+
}
55+
4256
bool QFieldAppAuthRequestHandler::hasPendingAuthRequest() const
4357
{
4458
if ( mBrowserAuthenticationOngoing )

src/core/qfieldappauthrequesthandler.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class QFieldAppAuthRequestHandler : public QObject, public QgsCredentials, publi
3838
{
3939
Q_OBJECT
4040

41+
Q_PROPERTY( bool isProjectLoading READ isProjectLoading WRITE setIsProjectLoading NOTIFY isProjectLoadingChanged )
4142
Q_PROPERTY( bool hasPendingAuthRequest READ hasPendingAuthRequest NOTIFY hasPendingAuthRequestChanged )
4243

4344
public:
@@ -60,6 +61,12 @@ class QFieldAppAuthRequestHandler : public QObject, public QgsCredentials, publi
6061
//! abort an ongoing external browser authentication request
6162
Q_INVOKABLE void abortAuthBrowser();
6263

64+
//! returns TRUE is a project is loading
65+
bool isProjectLoading() const;
66+
67+
//! sets whether a project is \a loading
68+
void setIsProjectLoading( bool loading );
69+
6370
//! returns the number of pending authentication requests
6471
bool hasPendingAuthRequest() const;
6572

@@ -69,6 +76,7 @@ class QFieldAppAuthRequestHandler : public QObject, public QgsCredentials, publi
6976
void reloadEverything();
7077
void showLoginBrowser( const QString &url );
7178
void hideLoginBrowser();
79+
void isProjectLoadingChanged();
7280
void hasPendingAuthRequestChanged();
7381

7482
protected:
@@ -102,6 +110,8 @@ class QFieldAppAuthRequestHandler : public QObject, public QgsCredentials, publi
102110

103111
QList<RealmEntry> mRealms;
104112
bool mBrowserAuthenticationOngoing = false;
113+
114+
bool mIsProjectLoading = false;
105115
};
106116

107117
#endif // QFIELDAPPAUTHREQUESTHANDLER_H

src/core/qfieldcloudconnection.cpp

Lines changed: 163 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <QTimer>
3131
#include <QUrlQuery>
3232
#include <qgsapplication.h>
33+
#include <qgsauthmanager.h>
3334
#include <qgsmessagelog.h>
3435
#include <qgsnetworkaccessmanager.h>
3536
#include <qgssettings.h>
@@ -39,12 +40,20 @@ QFieldCloudConnection::QFieldCloudConnection()
3940
: mUrl( QSettings().value( QStringLiteral( "/QFieldCloud/url" ), defaultUrl() ).toString() )
4041
, mUsername( QSettings().value( QStringLiteral( "/QFieldCloud/username" ) ).toString() )
4142
, mToken( QSettings().value( QStringLiteral( "/QFieldCloud/token" ) ).toByteArray() )
43+
, mProvider( QSettings().value( QStringLiteral( "/QFieldCloud/provider" ) ).toString() )
44+
, mProviderConfigId( QSettings().value( QStringLiteral( "/QFieldCloud/providerConfigId" ) ).toString() )
4245
{
4346
QgsNetworkAccessManager::instance()->setTimeout( 60 * 60 * 1000 );
4447
QgsNetworkAccessManager::instance()->setTransferTimeout( 5 * 60 * 1000 );
4548
// we cannot use "/" as separator, since QGIS puts a suffix QGIS/31700 anyway
4649
const QString userAgent = QStringLiteral( "qfield|%1|%2|%3|" ).arg( qfield::appVersion, qfield::appVersionStr.normalized( QString::NormalizationForm_KD ), qfield::gitRev );
4750
QgsSettings().setValue( QStringLiteral( "/qgis/networkAndProxy/userAgent" ), userAgent );
51+
52+
if ( !QgsApplication::authManager()->availableAuthMethodConfigs().contains( mProviderConfigId ) )
53+
{
54+
mProviderConfigId.clear();
55+
QSettings().remove( "/QFieldCloud/providerConfigId" );
56+
}
4857
}
4958

5059
QMap<QString, QString> QFieldCloudConnection::sErrors = QMap<QString, QString>(
@@ -104,14 +113,30 @@ QStringList QFieldCloudConnection::urls() const
104113
return savedUrls;
105114
}
106115

107-
QString QFieldCloudConnection::username() const
116+
QString QFieldCloudConnection::avatarUrl() const
108117
{
109-
return mUsername;
118+
return mAvatarUrl;
110119
}
111120

112-
QString QFieldCloudConnection::avatarUrl() const
121+
QString QFieldCloudConnection::provider() const
113122
{
114-
return mAvatarUrl;
123+
return mProvider;
124+
}
125+
126+
void QFieldCloudConnection::setProvider( const QString &provider )
127+
{
128+
if ( mProvider == provider )
129+
return;
130+
131+
mProvider = provider;
132+
QSettings().setValue( QStringLiteral( "/QFieldCloud/provider" ), provider );
133+
134+
emit providerChanged();
135+
}
136+
137+
QString QFieldCloudConnection::username() const
138+
{
139+
return mUsername;
115140
}
116141

117142
void QFieldCloudConnection::setUsername( const QString &username )
@@ -149,9 +174,74 @@ CloudUserInformation QFieldCloudConnection::userInformation() const
149174
return mUserInformation;
150175
}
151176

177+
bool QFieldCloudConnection::isFetchingAvailableProviders() const
178+
{
179+
return mIsFetchingAvailableProviders;
180+
}
181+
182+
QList<AuthenticationProvider> QFieldCloudConnection::availableProviders() const
183+
{
184+
return mAvailableProviders.values();
185+
}
186+
187+
void QFieldCloudConnection::getAuthenticationProviders()
188+
{
189+
if ( !mAvailableProviders.isEmpty() )
190+
{
191+
mAvailableProviders.clear();
192+
emit availableProvidersChanged();
193+
}
194+
195+
mIsFetchingAvailableProviders = true;
196+
emit isFetchingAvailableProvidersChanged();
197+
198+
QNetworkRequest request;
199+
request.setHeader( QNetworkRequest::ContentTypeHeader, "application/json" );
200+
request.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::RedirectPolicy::NoLessSafeRedirectPolicy );
201+
NetworkReply *reply = get( request, "/api/v1/auth/providers/" );
202+
203+
connect( reply, &NetworkReply::finished, this, [=]() {
204+
QNetworkReply *rawReply = reply->currentRawReply();
205+
206+
Q_ASSERT( reply->isFinished() );
207+
Q_ASSERT( rawReply );
208+
209+
reply->deleteLater();
210+
rawReply->deleteLater();
211+
212+
mIsFetchingAvailableProviders = false;
213+
emit isFetchingAvailableProvidersChanged();
214+
215+
if ( rawReply->error() != QNetworkReply::NoError )
216+
{
217+
return;
218+
}
219+
220+
const QVariantList providers = QJsonDocument::fromJson( rawReply->readAll() ).toVariant().toList();
221+
for ( const QVariant &provider : providers )
222+
{
223+
const QVariantMap providerDetails = provider.toMap();
224+
const QString providerId = providerDetails.value( QStringLiteral( "id" ) ).toString();
225+
mAvailableProviders[providerId] = AuthenticationProvider( providerId, providerDetails.value( QStringLiteral( "name" ) ).toString(), providerDetails );
226+
}
227+
emit availableProvidersChanged();
228+
} );
229+
}
230+
152231
void QFieldCloudConnection::login()
153232
{
154-
const bool loginUsingToken = !mToken.isEmpty() && ( mPassword.isEmpty() || mUsername.isEmpty() );
233+
if ( !mProvider.isEmpty() )
234+
{
235+
if ( mProviderConfigId.isEmpty() && !mAvailableProviders.contains( mProvider ) )
236+
{
237+
emit loginFailed( tr( "Authentication provider missing" ) );
238+
return;
239+
}
240+
}
241+
242+
setStatus( ConnectionStatus::Connecting );
243+
244+
const bool loginUsingToken = !mProvider.isEmpty() || ( !mToken.isEmpty() && ( mPassword.isEmpty() || mUsername.isEmpty() ) );
155245
NetworkReply *reply = loginUsingToken
156246
? get( QStringLiteral( "/api/v1/auth/user/" ) )
157247
: post( QStringLiteral( "/api/v1/auth/token/" ), QVariantMap(
@@ -160,8 +250,6 @@ void QFieldCloudConnection::login()
160250
{ "password", mPassword },
161251
} ) );
162252

163-
setStatus( ConnectionStatus::Connecting );
164-
165253
// Handle login redirect as an error state
166254
connect( reply, &NetworkReply::redirected, this, [=]() {
167255
QNetworkReply *rawReply = reply->currentRawReply();
@@ -272,6 +360,14 @@ void QFieldCloudConnection::logout()
272360
mAvatarUrl.clear();
273361
emit avatarUrlChanged();
274362

363+
if ( !mProviderConfigId.isEmpty() )
364+
{
365+
QgsApplication::instance()->authManager()->removeAuthenticationConfig( mProviderConfigId );
366+
mProviderConfigId.clear();
367+
QSettings().remove( "/QFieldCloud/providerConfigId" );
368+
emit providerConfigurationChanged();
369+
}
370+
275371
setStatus( ConnectionStatus::Disconnected );
276372
}
277373

@@ -469,6 +565,66 @@ void QFieldCloudConnection::setAuthenticationToken( QNetworkRequest &request )
469565
{
470566
request.setRawHeader( "Authorization", "Token " + mToken );
471567
}
568+
569+
if ( !mProvider.isEmpty() )
570+
{
571+
if ( mProviderConfigId.isEmpty() && mAvailableProviders.contains( mProvider ) )
572+
{
573+
const QVariantMap providerDetails = mAvailableProviders[mProvider].details();
574+
const QVariantMap providerExtraTokens = providerDetails.value( "extra_tokens" ).toMap();
575+
QStringList extraTokens;
576+
for ( const QString &key : providerExtraTokens.keys() )
577+
{
578+
extraTokens << QStringLiteral( "\"%1\":\"%2\"" ).arg( key, providerExtraTokens.value( key ).toString() );
579+
}
580+
581+
QgsAuthMethodConfig config;
582+
config.setName( "qfieldcloud-sso" );
583+
config.setMethod( "OAuth2" );
584+
config.setConfig( "oauth2config", QStringLiteral( "{\"accessMethod\":0,"
585+
" \"apiKey\":null,"
586+
" \"clientId\":\"%1\","
587+
" \"clientSecret\":\"%2\","
588+
" \"configType\":1,"
589+
" \"customHeader\":null,"
590+
" \"description\":\"\","
591+
" \"grantFlow\":0,"
592+
" \"id\":null,"
593+
" \"name\":null,"
594+
" \"objectName\":\"\","
595+
" \"password\":null,"
596+
" \"persistToken\":true,"
597+
" \"queryPairs\":{\"1\":\"1\"},"
598+
" \"redirectHost\":\"localhost\","
599+
" \"redirectPort\":7070,"
600+
" \"redirectUrl\":null,"
601+
" \"requestUrl\":\"%3\","
602+
" \"tokenUrl\":\"%4\","
603+
" \"refreshTokenUrl\":\"%5\","
604+
" \"scope\":\"%6\","
605+
" \"extraTokens\":{%7},"
606+
" \"requestTimeout\":30,"
607+
" \"username\":null,"
608+
" \"version\":1"
609+
"}" )
610+
.arg( providerDetails.value( "client_id" ).toString(),
611+
providerDetails.value( "client_secret" ).toString(),
612+
providerDetails.value( "request_url" ).toString(),
613+
providerDetails.value( "token_url" ).toString(),
614+
providerDetails.value( "refresh_token_url" ).toString(),
615+
providerDetails.value( "scope" ).toString(),
616+
extraTokens.join( ',' ) ) );
617+
qDebug() << config.config( "oauth2config" );
618+
QgsApplication::instance()->authManager()->storeAuthenticationConfig( config, true );
619+
620+
mProviderConfigId = config.id();
621+
qDebug() << mProviderConfigId;
622+
QSettings().setValue( QStringLiteral( "/QFieldCloud/providerConfigId" ), mProviderConfigId );
623+
emit providerConfigurationChanged();
624+
}
625+
626+
QgsApplication::instance()->authManager()->updateNetworkRequest( request, mProviderConfigId );
627+
}
472628
}
473629

474630
void QFieldCloudConnection::setClientHeaders( QNetworkRequest &request )

0 commit comments

Comments
 (0)