30
30
#include < QTimer>
31
31
#include < QUrlQuery>
32
32
#include < qgsapplication.h>
33
+ #include < qgsauthmanager.h>
33
34
#include < qgsmessagelog.h>
34
35
#include < qgsnetworkaccessmanager.h>
35
36
#include < qgssettings.h>
@@ -39,12 +40,20 @@ QFieldCloudConnection::QFieldCloudConnection()
39
40
: mUrl( QSettings().value( QStringLiteral( " /QFieldCloud/url" ), defaultUrl() ).toString() )
40
41
, mUsername( QSettings().value( QStringLiteral( " /QFieldCloud/username" ) ).toString() )
41
42
, mToken( QSettings().value( QStringLiteral( " /QFieldCloud/token" ) ).toByteArray() )
43
+ , mProvider( QSettings().value( QStringLiteral( " /QFieldCloud/provider" ) ).toString() )
44
+ , mProviderConfigId( QSettings().value( QStringLiteral( " /QFieldCloud/providerConfigId" ) ).toString() )
42
45
{
43
46
QgsNetworkAccessManager::instance ()->setTimeout ( 60 * 60 * 1000 );
44
47
QgsNetworkAccessManager::instance ()->setTransferTimeout ( 5 * 60 * 1000 );
45
48
// we cannot use "/" as separator, since QGIS puts a suffix QGIS/31700 anyway
46
49
const QString userAgent = QStringLiteral ( " qfield|%1|%2|%3|" ).arg ( qfield::appVersion, qfield::appVersionStr.normalized ( QString::NormalizationForm_KD ), qfield::gitRev );
47
50
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
+ }
48
57
}
49
58
50
59
QMap<QString, QString> QFieldCloudConnection::sErrors = QMap<QString, QString>(
@@ -104,14 +113,30 @@ QStringList QFieldCloudConnection::urls() const
104
113
return savedUrls;
105
114
}
106
115
107
- QString QFieldCloudConnection::username () const
116
+ QString QFieldCloudConnection::avatarUrl () const
108
117
{
109
- return mUsername ;
118
+ return mAvatarUrl ;
110
119
}
111
120
112
- QString QFieldCloudConnection::avatarUrl () const
121
+ QString QFieldCloudConnection::provider () const
113
122
{
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 ;
115
140
}
116
141
117
142
void QFieldCloudConnection::setUsername ( const QString &username )
@@ -149,9 +174,74 @@ CloudUserInformation QFieldCloudConnection::userInformation() const
149
174
return mUserInformation ;
150
175
}
151
176
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
+
152
231
void QFieldCloudConnection::login ()
153
232
{
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 () ) );
155
245
NetworkReply *reply = loginUsingToken
156
246
? get ( QStringLiteral ( " /api/v1/auth/user/" ) )
157
247
: post ( QStringLiteral ( " /api/v1/auth/token/" ), QVariantMap (
@@ -160,8 +250,6 @@ void QFieldCloudConnection::login()
160
250
{ " password" , mPassword },
161
251
} ) );
162
252
163
- setStatus ( ConnectionStatus::Connecting );
164
-
165
253
// Handle login redirect as an error state
166
254
connect ( reply, &NetworkReply::redirected, this , [=]() {
167
255
QNetworkReply *rawReply = reply->currentRawReply ();
@@ -212,6 +300,14 @@ void QFieldCloudConnection::login()
212
300
emit loginFailed ( message );
213
301
}
214
302
303
+ if ( !mProvider .isEmpty () && !mProviderConfigId .isEmpty () )
304
+ {
305
+ QgsApplication::instance ()->authManager ()->removeAuthenticationConfig ( mProviderConfigId );
306
+ mProviderConfigId .clear ();
307
+ QSettings ().remove ( " /QFieldCloud/providerConfigId" );
308
+ emit providerConfigurationChanged ();
309
+ }
310
+
215
311
setStatus ( ConnectionStatus::Disconnected );
216
312
return ;
217
313
}
@@ -258,7 +354,7 @@ void QFieldCloudConnection::logout()
258
354
QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance ();
259
355
QNetworkRequest request ( mUrl + QStringLiteral ( " /api/v1/auth/logout/" ) );
260
356
request.setHeader ( QNetworkRequest::ContentTypeHeader, " application/json" );
261
- setAuthenticationToken ( request );
357
+ setAuthenticationDetails ( request );
262
358
263
359
QNetworkReply *reply = nam->post ( request, QByteArray () );
264
360
@@ -272,6 +368,14 @@ void QFieldCloudConnection::logout()
272
368
mAvatarUrl .clear ();
273
369
emit avatarUrlChanged ();
274
370
371
+ if ( !mProviderConfigId .isEmpty () )
372
+ {
373
+ QgsApplication::instance ()->authManager ()->removeAuthenticationConfig ( mProviderConfigId );
374
+ mProviderConfigId .clear ();
375
+ QSettings ().remove ( " /QFieldCloud/providerConfigId" );
376
+ emit providerConfigurationChanged ();
377
+ }
378
+
275
379
setStatus ( ConnectionStatus::Disconnected );
276
380
}
277
381
@@ -289,7 +393,7 @@ NetworkReply *QFieldCloudConnection::post( const QString &endpoint, const QVaria
289
393
{
290
394
QNetworkRequest request ( mUrl + endpoint );
291
395
QByteArray requestBody = QJsonDocument ( QJsonObject::fromVariantMap ( params ) ).toJson ();
292
- setAuthenticationToken ( request );
396
+ setAuthenticationDetails ( request );
293
397
request.setAttribute ( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::RedirectPolicy::NoLessSafeRedirectPolicy );
294
398
295
399
if ( fileNames.isEmpty () )
@@ -360,7 +464,7 @@ NetworkReply *QFieldCloudConnection::get( const QString &endpoint, const QVarian
360
464
361
465
request.setHeader ( QNetworkRequest::ContentTypeHeader, " application/json" );
362
466
request.setAttribute ( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::RedirectPolicy::NoLessSafeRedirectPolicy );
363
- setAuthenticationToken ( request );
467
+ setAuthenticationDetails ( request );
364
468
365
469
return get ( request, endpoint, params );
366
470
}
@@ -463,12 +567,70 @@ void QFieldCloudConnection::setState( ConnectionState state )
463
567
emit stateChanged ();
464
568
}
465
569
466
- void QFieldCloudConnection::setAuthenticationToken ( QNetworkRequest &request )
570
+ void QFieldCloudConnection::setAuthenticationDetails ( QNetworkRequest &request )
467
571
{
468
572
if ( !mToken .isNull () )
469
573
{
470
574
request.setRawHeader ( " Authorization" , " Token " + mToken );
471
575
}
576
+
577
+ if ( !mProvider .isEmpty () )
578
+ {
579
+ QString providerId;
580
+ if ( mProviderConfigId .isEmpty () && mAvailableProviders .contains ( mProvider ) )
581
+ {
582
+ const QVariantMap providerDetails = mAvailableProviders [mProvider ].details ();
583
+ providerId = providerDetails.value ( " id" ).toString ();
584
+
585
+ QVariantMap configMap;
586
+ configMap[" accessMethod" ] = 0 ;
587
+ configMap[" clientId" ] = providerDetails.value ( " client_id" ).toString ();
588
+ configMap[" clientSecret" ] = providerDetails.value ( " client_secret" ).toString ();
589
+ configMap[" configType" ] = 1 ;
590
+ configMap[" description" ] = QString ( " Connection details for QFieldCloud using %1 provider" ).arg ( mProvider );
591
+ configMap[" extraTokens" ] = providerDetails.value ( " extra_tokens" ).toMap ();
592
+ configMap[" grantFlow" ] = providerDetails.value ( " grant_flow" ).toInt ();
593
+ configMap[" name" ] = QString ( " Autogenerated by QField" );
594
+ configMap[" persistToken" ] = true ;
595
+ configMap[" redirectHost" ] = QString ( " localhost" );
596
+ configMap[" redirectPort" ] = 7070 ;
597
+ configMap[" refreshTokenUrl" ] = providerDetails.value ( " refresh_token_url" ).toString ();
598
+ configMap[" requestTimeout" ] = 30 ;
599
+ configMap[" requestUrl" ] = providerDetails.value ( " request_url" ).toString ();
600
+ configMap[" scope" ] = providerDetails.value ( " scope" ).toString ();
601
+ configMap[" tokenUrl" ] = providerDetails.value ( " token_url" ).toString ();
602
+ configMap[" version" ] = 1 ;
603
+ QJsonDocument json = QJsonDocument::fromVariant ( configMap );
604
+
605
+ QgsAuthMethodConfig config;
606
+ config.setName ( " qfieldcloud-sso" );
607
+ config.setMethod ( " OAuth2" );
608
+ config.setConfig ( " oauth2config" , json.toJson () );
609
+ config.setConfig ( " qfieldcloud-sso-id" , providerId );
610
+ QgsApplication::instance ()->authManager ()->storeAuthenticationConfig ( config, true );
611
+
612
+ mProviderConfigId = config.id ();
613
+ QSettings ().setValue ( QStringLiteral ( " /QFieldCloud/providerConfigId" ), mProviderConfigId );
614
+ emit providerConfigurationChanged ();
615
+ }
616
+ else
617
+ {
618
+ QgsAuthMethodConfig config;
619
+ QgsApplication::instance ()->authManager ()->loadAuthenticationConfig ( mProviderConfigId , config, true );
620
+ providerId = config.config ( " qfieldcloud-sso-id" );
621
+ }
622
+
623
+ QgsApplication::instance ()->authManager ()->updateNetworkRequest ( request, mProviderConfigId );
624
+ request.setRawHeader ( " X-QFC-IDP-ID" , providerId.toLatin1 () );
625
+
626
+ const QList<QNetworkCookie> cookies = QgsNetworkAccessManager::instance ()->cookieJar ()->cookiesForUrl ( mUrl );
627
+ auto match = std::find_if ( cookies.begin (), cookies.end (), []( const QNetworkCookie &cookie ) { return cookie.name () == QLatin1String ( " csrftoken" ); } );
628
+ if ( match != cookies.end () )
629
+ {
630
+ request.setRawHeader ( " X-CSRFToken" , match->value () );
631
+ request.setRawHeader ( " Referer" , mUrl .toLatin1 () );
632
+ }
633
+ }
472
634
}
473
635
474
636
void QFieldCloudConnection::setClientHeaders ( QNetworkRequest &request )
0 commit comments