1 /***************************************************************************
2 begin : July 13, 2016
3 copyright : (C) 2016 by Monsanto Company, USA
4 author : Larry Shaffer, Boundless Spatial
5 email : lshaffer at boundlessgeo dot com
6 ***************************************************************************
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 ***************************************************************************/
14
15 #include "qgsauthoauth2edit.h"
16 #include "ui_qgsauthoauth2edit.h"
17
18 #include <QDir>
19 #include <QFileDialog>
20 #include <QDesktopServices>
21 #include <QUrl>
22
23 #include "Json.h"
24
25 #include "qgsapplication.h"
26 #include "qgsauthguiutils.h"
27 #include "qgsauthmanager.h"
28 #include "qgsauthconfigedit.h"
29 #include "qgsmessagelog.h"
30 #include "qgsnetworkaccessmanager.h"
31
QgsAuthOAuth2Edit(QWidget * parent)32 QgsAuthOAuth2Edit::QgsAuthOAuth2Edit( QWidget *parent )
33 : QgsAuthMethodEdit( parent )
34 , mDefinedConfigsCache( QgsStringMap() )
35 {
36 setupUi( this );
37
38 initGui();
39
40 initConfigObjs();
41
42 populateGrantFlows();
43 updateGrantFlow( static_cast<int>( QgsAuthOAuth2Config::AuthCode ) ); // first index: Authorization Code
44
45 populateAccessMethods();
46
47 queryTableSelectionChanged();
48
49 loadDefinedConfigs();
50
51 setupConnections();
52
53 loadFromOAuthConfig( mOAuthConfigCustom.get() );
54 updatePredefinedLocationsTooltip();
55
56 pteDefinedDesc->setOpenLinks( false );
57 connect( pteDefinedDesc, &QTextBrowser::anchorClicked, this, [ = ]( const QUrl & url )
58 {
59 QDesktopServices::openUrl( url );
60 } );
61 }
62
63
initGui()64 void QgsAuthOAuth2Edit::initGui()
65 {
66 mParentName = parentNameField();
67
68 frameNotify->setVisible( false );
69
70 // TODO: add messagebar to notify frame?
71
72 tabConfigs->setCurrentIndex( customTab() );
73
74 btnExport->setEnabled( false );
75
76 chkbxTokenPersist->setChecked( false );
77
78 grpbxAdvanced->setCollapsed( true );
79 grpbxAdvanced->setFlat( false );
80
81 btnTokenClear = new QToolButton( this );
82 btnTokenClear->setObjectName( QStringLiteral( "btnTokenClear" ) );
83 btnTokenClear->setMaximumHeight( 20 );
84 btnTokenClear->setText( tr( "Tokens" ) );
85 btnTokenClear->setToolTip( tr( "Remove cached tokens" ) );
86 btnTokenClear->setIcon( QIcon( QStringLiteral( ":/oauth2method/svg/close.svg" ) ) );
87 btnTokenClear->setIconSize( QSize( 12, 12 ) );
88 btnTokenClear->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
89 btnTokenClear->setEnabled( hasTokenCacheFile() );
90
91 connect( btnTokenClear, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::removeTokenCacheFile );
92 tabConfigs->setCornerWidget( btnTokenClear, Qt::TopRightCorner );
93 }
94
parentWidget() const95 QWidget *QgsAuthOAuth2Edit::parentWidget() const
96 {
97 if ( !window() )
98 {
99 return nullptr;
100 }
101
102 const QMetaObject *metaObject = window()->metaObject();
103 const QString parentclass = metaObject->className();
104 //QgsDebugMsg( QStringLiteral( "parent class: %1" ).arg( parentclass ) );
105 if ( parentclass != QLatin1String( "QgsAuthConfigEdit" ) )
106 {
107 QgsDebugMsg( QStringLiteral( "Parent widget not QgsAuthConfigEdit instance" ) );
108 return nullptr;
109 }
110
111 return window();
112 }
113
parentNameField() const114 QLineEdit *QgsAuthOAuth2Edit::parentNameField() const
115 {
116 return parentWidget() ? parentWidget()->findChild<QLineEdit *>( QStringLiteral( "leName" ) ) : nullptr;
117 }
118
parentConfigId() const119 QString QgsAuthOAuth2Edit::parentConfigId() const
120 {
121 if ( !parentWidget() )
122 {
123 return QString();
124 }
125
126 QgsAuthConfigEdit *cie = qobject_cast<QgsAuthConfigEdit *>( parentWidget() );
127 if ( !cie )
128 {
129 QgsDebugMsg( QStringLiteral( "Could not cast to QgsAuthConfigEdit" ) );
130 return QString();
131 }
132
133 if ( cie->configId().isEmpty() )
134 {
135 QgsDebugMsg( QStringLiteral( "QgsAuthConfigEdit->configId() is empty" ) );
136 }
137
138 return cie->configId();
139 }
140
141
setupConnections()142 void QgsAuthOAuth2Edit::setupConnections()
143 {
144 // Action and interaction connections
145 connect( tabConfigs, &QTabWidget::currentChanged, this, &QgsAuthOAuth2Edit::tabIndexChanged );
146
147 connect( btnExport, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::exportOAuthConfig );
148 connect( btnImport, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::importOAuthConfig );
149
150 connect( tblwdgQueryPairs, &QTableWidget::itemSelectionChanged, this, &QgsAuthOAuth2Edit::queryTableSelectionChanged );
151
152 connect( btnAddQueryPair, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::addQueryPair );
153 connect( btnRemoveQueryPair, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::removeQueryPair );
154
155 connect( lstwdgDefinedConfigs, &QListWidget::currentItemChanged, this, &QgsAuthOAuth2Edit::currentDefinedItemChanged );
156
157 connect( btnGetDefinedDirPath, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::getDefinedCustomDir );
158 connect( leDefinedDirPath, &QLineEdit::textChanged, this, &QgsAuthOAuth2Edit::definedCustomDirChanged );
159
160 connect( btnSoftStatementDir, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::getSoftStatementDir );
161 connect( leSoftwareStatementJwtPath, &QLineEdit::textChanged, this, &QgsAuthOAuth2Edit::softwareStatementJwtPathChanged );
162 connect( leSoftwareStatementConfigUrl, &QLineEdit::textChanged, this, [ = ]( const QString & txt )
163 {
164 btnRegister->setEnabled( ! leSoftwareStatementJwtPath->text().isEmpty()
165 && ( QUrl( txt ).isValid() || ! mRegistrationEndpoint.isEmpty() ) );
166 } );
167 connect( btnRegister, &QPushButton::clicked, this, &QgsAuthOAuth2Edit::getSoftwareStatementConfig );
168
169 // Custom config editing connections
170 connect( cmbbxGrantFlow, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
171 this, &QgsAuthOAuth2Edit::updateGrantFlow ); // also updates GUI
172 connect( pteDescription, &QPlainTextEdit::textChanged, this, &QgsAuthOAuth2Edit::descriptionChanged );
173 connect( leRequestUrl, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRequestUrl );
174 connect( leTokenUrl, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setTokenUrl );
175 connect( leRefreshTokenUrl, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRefreshTokenUrl );
176 connect( leRedirectUrl, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRedirectUrl );
177 connect( spnbxRedirectPort, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ),
178 mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRedirectPort );
179 connect( leClientId, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setClientId );
180 connect( leClientSecret, &QgsPasswordLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setClientSecret );
181 connect( leUsername, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setUsername );
182 connect( lePassword, &QgsPasswordLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setPassword );
183 connect( leScope, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setScope );
184 connect( leApiKey, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setApiKey );
185 connect( chkbxTokenPersist, &QCheckBox::toggled, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setPersistToken );
186 connect( cmbbxAccessMethod, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
187 this, &QgsAuthOAuth2Edit::updateConfigAccessMethod );
188 connect( spnbxRequestTimeout, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ),
189 mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRequestTimeout );
190
191 connect( mTokenHeaderLineEdit, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setCustomHeader );
192
193 connect( mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::validityChanged, this, &QgsAuthOAuth2Edit::configValidityChanged );
194
195 if ( mParentName )
196 {
197 connect( mParentName, &QLineEdit::textChanged, this, &QgsAuthOAuth2Edit::configValidityChanged );
198 }
199 }
200
201
configValidityChanged()202 void QgsAuthOAuth2Edit::configValidityChanged()
203 {
204 validateConfig();
205 const bool parentname = mParentName && !mParentName->text().isEmpty();
206 btnExport->setEnabled( mValid && parentname );
207 }
208
validateConfig()209 bool QgsAuthOAuth2Edit::validateConfig()
210 {
211 const bool curvalid = ( onCustomTab() ? mOAuthConfigCustom->isValid() : !mDefinedId.isEmpty() );
212 if ( mValid != curvalid )
213 {
214 mValid = curvalid;
215 emit validityChanged( curvalid );
216 }
217 return curvalid;
218 }
219
configMap() const220 QgsStringMap QgsAuthOAuth2Edit::configMap() const
221 {
222 QgsStringMap configmap;
223 bool ok = false;
224
225 if ( onCustomTab() )
226 {
227 if ( !mOAuthConfigCustom || !mOAuthConfigCustom->isValid() )
228 {
229 QgsDebugMsg( QStringLiteral( "FAILED to serialize OAuth config object: null or invalid object" ) );
230 return configmap;
231 }
232
233 mOAuthConfigCustom->setQueryPairs( queryPairs() );
234
235 const QByteArray configtxt = mOAuthConfigCustom->saveConfigTxt( QgsAuthOAuth2Config::JSON, false, &ok );
236
237 if ( !ok )
238 {
239 QgsDebugMsg( QStringLiteral( "FAILED to serialize OAuth config object" ) );
240 return configmap;
241 }
242
243 if ( configtxt.isEmpty() )
244 {
245 QgsDebugMsg( QStringLiteral( "FAILED to serialize OAuth config object: content empty" ) );
246 return configmap;
247 }
248
249 //###################### DO NOT LEAVE ME UNCOMMENTED #####################
250 //QgsDebugMsg( QStringLiteral( "SAVE oauth2config configtxt: \n\n%1\n\n" ).arg( QString( configtxt ) ) );
251 //###################### DO NOT LEAVE ME UNCOMMENTED #####################
252
253 configmap.insert( QStringLiteral( "oauth2config" ), QString( configtxt ) );
254
255 updateTokenCacheFile( mOAuthConfigCustom->persistToken() );
256 }
257 else if ( onDefinedTab() && !mDefinedId.isEmpty() )
258 {
259 configmap.insert( QStringLiteral( "definedid" ), mDefinedId );
260 configmap.insert( QStringLiteral( "defineddirpath" ), leDefinedDirPath->text() );
261 configmap.insert( QStringLiteral( "querypairs" ),
262 QgsAuthOAuth2Config::serializeFromVariant(
263 queryPairs(), QgsAuthOAuth2Config::JSON, false ) );
264 }
265
266 return configmap;
267 }
268
loadConfig(const QgsStringMap & configmap)269 void QgsAuthOAuth2Edit::loadConfig( const QgsStringMap &configmap )
270 {
271 clearConfig();
272
273 mConfigMap = configmap;
274 bool ok = false;
275
276 //QgsDebugMsg( QStringLiteral( "oauth2config: " ).arg( configmap.value( QStringLiteral( "oauth2config" ) ) ) );
277
278 if ( configmap.contains( QStringLiteral( "oauth2config" ) ) )
279 {
280 tabConfigs->setCurrentIndex( customTab() );
281 const QByteArray configtxt = configmap.value( QStringLiteral( "oauth2config" ) ).toUtf8();
282 if ( !configtxt.isEmpty() )
283 {
284 //###################### DO NOT LEAVE ME UNCOMMENTED #####################
285 //QgsDebugMsg( QStringLiteral( "LOAD oauth2config configtxt: \n\n%1\n\n" ).arg( QString( configtxt ) ) );
286 //###################### DO NOT LEAVE ME UNCOMMENTED #####################
287
288 if ( !mOAuthConfigCustom->loadConfigTxt( configtxt, QgsAuthOAuth2Config::JSON ) )
289 {
290 QgsDebugMsg( QStringLiteral( "FAILED to load OAuth2 config into object" ) );
291 }
292
293 //###################### DO NOT LEAVE ME UNCOMMENTED #####################
294 //QVariantMap vmap = mOAuthConfigCustom->mappedProperties();
295 //QByteArray vmaptxt = QgsAuthOAuth2Config::serializeFromVariant(vmap, QgsAuthOAuth2Config::JSON, true );
296 //QgsDebugMsg( QStringLiteral( "LOAD oauth2config vmaptxt: \n\n%1\n\n" ).arg( QString( vmaptxt ) ) );
297 //###################### DO NOT LEAVE ME UNCOMMENTED #####################
298
299 // could only be loading defaults at this point
300 loadFromOAuthConfig( mOAuthConfigCustom.get() );
301
302 mPrevPersistToken = mOAuthConfigCustom->persistToken();
303 }
304 else
305 {
306 QgsDebugMsg( QStringLiteral( "FAILED to load OAuth2 config: empty config txt" ) );
307 }
308 }
309 else if ( configmap.contains( QStringLiteral( "definedid" ) ) )
310 {
311 tabConfigs->setCurrentIndex( definedTab() );
312 const QString definedid = configmap.value( QStringLiteral( "definedid" ) );
313 setCurrentDefinedConfig( definedid );
314 if ( !definedid.isEmpty() )
315 {
316 if ( !configmap.value( QStringLiteral( "defineddirpath" ) ).isEmpty() )
317 {
318 // this will trigger a reload of dirs and a reselection of any existing defined id
319 leDefinedDirPath->setText( configmap.value( QStringLiteral( "defineddirpath" ) ) );
320 }
321 else
322 {
323 QgsDebugMsg( QStringLiteral( "No custom defined dir path to load OAuth2 config" ) );
324 selectCurrentDefinedConfig();
325 }
326
327 const QByteArray querypairstxt = configmap.value( QStringLiteral( "querypairs" ) ).toUtf8();
328 if ( !querypairstxt.isNull() && !querypairstxt.isEmpty() )
329 {
330 const QVariantMap querypairsmap =
331 QgsAuthOAuth2Config::variantFromSerialized( querypairstxt, QgsAuthOAuth2Config::JSON, &ok );
332 if ( ok )
333 {
334 populateQueryPairs( querypairsmap );
335 }
336 else
337 {
338 QgsDebugMsg( QStringLiteral( "No query pairs to load OAuth2 config: failed to parse" ) );
339 }
340 }
341 else
342 {
343 QgsDebugMsg( QStringLiteral( "No query pairs to load OAuth2 config: empty text" ) );
344 }
345 }
346 else
347 {
348 QgsDebugMsg( QStringLiteral( "FAILED to load a defined ID for OAuth2 config" ) );
349 }
350 }
351
352 validateConfig();
353 }
354
resetConfig()355 void QgsAuthOAuth2Edit::resetConfig()
356 {
357 loadConfig( mConfigMap );
358 }
359
clearConfig()360 void QgsAuthOAuth2Edit::clearConfig()
361 {
362 // restore defaults to config objs
363 mOAuthConfigCustom->setToDefaults();
364
365 mDefinedId.clear();
366
367 clearQueryPairs();
368
369 // clear any set predefined location
370 leDefinedDirPath->clear();
371
372 // reload predefined table
373 loadDefinedConfigs();
374
375 loadFromOAuthConfig( mOAuthConfigCustom.get() );
376 }
377
loadFromOAuthConfig(const QgsAuthOAuth2Config * config)378 void QgsAuthOAuth2Edit::loadFromOAuthConfig( const QgsAuthOAuth2Config *config )
379 {
380 if ( !config )
381 {
382 return;
383 }
384
385 // load relative to config type
386 if ( config->configType() == QgsAuthOAuth2Config::Custom )
387 {
388 if ( config->isValid() )
389 {
390 tabConfigs->setCurrentIndex( customTab() );
391 }
392 pteDescription->setPlainText( config->description() );
393 leRequestUrl->setText( config->requestUrl() );
394 leTokenUrl->setText( config->tokenUrl() );
395 leRefreshTokenUrl->setText( config->refreshTokenUrl() );
396 leRedirectUrl->setText( config->redirectUrl() );
397 spnbxRedirectPort->setValue( config->redirectPort() );
398 leClientId->setText( config->clientId() );
399 leClientSecret->setText( config->clientSecret() );
400 leUsername->setText( config->username() );
401 lePassword->setText( config->password() );
402 leScope->setText( config->scope() );
403 leApiKey->setText( config->apiKey() );
404 mTokenHeaderLineEdit->setText( config->customHeader() );
405
406 // advanced
407 chkbxTokenPersist->setChecked( config->persistToken() );
408 cmbbxAccessMethod->setCurrentIndex( static_cast<int>( config->accessMethod() ) );
409 spnbxRequestTimeout->setValue( config->requestTimeout() );
410
411 populateQueryPairs( config->queryPairs() );
412
413 updateGrantFlow( static_cast<int>( config->grantFlow() ) );
414 }
415
416 validateConfig();
417 }
418
updateTokenCacheFile(bool curpersist) const419 void QgsAuthOAuth2Edit::updateTokenCacheFile( bool curpersist ) const
420 {
421 // default for unset persistToken in config and edit GUI is false
422 if ( mPrevPersistToken == curpersist )
423 {
424 return;
425 }
426
427 if ( !parent() )
428 {
429 QgsDebugMsg( QStringLiteral( "Edit widget has no parent" ) );
430 return;
431 }
432
433 const QString authcfg = parentConfigId();
434 if ( authcfg.isEmpty() )
435 {
436 QgsDebugMsg( QStringLiteral( "Auth config ID empty in ID widget of parent" ) );
437 return;
438 }
439
440 const QString localcachefile = QgsAuthOAuth2Config::tokenCachePath( authcfg, false );
441
442 const QString tempcachefile = QgsAuthOAuth2Config::tokenCachePath( authcfg, true );
443
444 //QgsDebugMsg( QStringLiteral( "localcachefile: %1" ).arg( localcachefile ) );
445 //QgsDebugMsg( QStringLiteral( "tempcachefile: %1" ).arg( tempcachefile ) );
446
447 if ( curpersist )
448 {
449 // move cache file from temp dir to local
450 if ( QFile::exists( localcachefile ) && !QFile::remove( localcachefile ) )
451 {
452 QgsDebugMsg( QStringLiteral( "FAILED to delete local token cache file: %1" ).arg( localcachefile ) );
453 return;
454 }
455 if ( QFile::exists( tempcachefile ) && !QFile::copy( tempcachefile, localcachefile ) )
456 {
457 QgsDebugMsg( QStringLiteral( "FAILED to copy temp to local token cache file: %1 -> %2" ).arg( tempcachefile, localcachefile ) );
458 return;
459 }
460 if ( QFile::exists( tempcachefile ) && !QFile::remove( tempcachefile ) )
461 {
462 QgsDebugMsg( QStringLiteral( "FAILED to delete temp token cache file after copy: %1" ).arg( tempcachefile ) );
463 return;
464 }
465 }
466 else
467 {
468 // move cache file from local to temp
469 if ( QFile::exists( tempcachefile ) && !QFile::remove( tempcachefile ) )
470 {
471 QgsDebugMsg( QStringLiteral( "FAILED to delete temp token cache file: %1" ).arg( tempcachefile ) );
472 return;
473 }
474 if ( QFile::exists( localcachefile ) && !QFile::copy( localcachefile, tempcachefile ) )
475 {
476 QgsDebugMsg( QStringLiteral( "FAILED to copy local to temp token cache file: %1 -> %2" ).arg( localcachefile, tempcachefile ) );
477 return;
478 }
479 if ( QFile::exists( localcachefile ) && !QFile::remove( localcachefile ) )
480 {
481 QgsDebugMsg( QStringLiteral( "FAILED to delete temp token cache file after copy: %1" ).arg( localcachefile ) );
482 return;
483 }
484 }
485 }
486
487
tabIndexChanged(int indx)488 void QgsAuthOAuth2Edit::tabIndexChanged( int indx )
489 {
490 mCurTab = indx;
491 validateConfig();
492 }
493
494
populateGrantFlows()495 void QgsAuthOAuth2Edit::populateGrantFlows()
496 {
497 cmbbxGrantFlow->addItem( QgsAuthOAuth2Config::grantFlowString( QgsAuthOAuth2Config::AuthCode ),
498 static_cast<int>( QgsAuthOAuth2Config::AuthCode ) );
499 cmbbxGrantFlow->addItem( QgsAuthOAuth2Config::grantFlowString( QgsAuthOAuth2Config::Implicit ),
500 static_cast<int>( QgsAuthOAuth2Config::Implicit ) );
501 cmbbxGrantFlow->addItem( QgsAuthOAuth2Config::grantFlowString( QgsAuthOAuth2Config::ResourceOwner ),
502 static_cast<int>( QgsAuthOAuth2Config::ResourceOwner ) );
503 }
504
505
definedCustomDirChanged(const QString & path)506 void QgsAuthOAuth2Edit::definedCustomDirChanged( const QString &path )
507 {
508 const QFileInfo pinfo( path );
509 const bool ok = pinfo.exists() || pinfo.isDir();
510
511 leDefinedDirPath->setStyleSheet( ok ? QString() : QgsAuthGuiUtils::redTextStyleSheet() );
512 updatePredefinedLocationsTooltip();
513
514 if ( ok )
515 {
516 loadDefinedConfigs();
517 }
518 }
519
520
softwareStatementJwtPathChanged(const QString & path)521 void QgsAuthOAuth2Edit::softwareStatementJwtPathChanged( const QString &path )
522 {
523 const QFileInfo pinfo( path );
524 const bool ok = pinfo.exists() || pinfo.isFile();
525
526 leSoftwareStatementJwtPath->setStyleSheet( ok ? QString() : QgsAuthGuiUtils::redTextStyleSheet() );
527
528 if ( ok )
529 {
530 parseSoftwareStatement( path );
531 }
532 }
533
534
setCurrentDefinedConfig(const QString & id)535 void QgsAuthOAuth2Edit::setCurrentDefinedConfig( const QString &id )
536 {
537 mDefinedId = id;
538 QgsDebugMsg( QStringLiteral( "Set defined ID: %1" ).arg( id ) );
539 validateConfig();
540 }
541
currentDefinedItemChanged(QListWidgetItem * cur,QListWidgetItem * prev)542 void QgsAuthOAuth2Edit::currentDefinedItemChanged( QListWidgetItem *cur, QListWidgetItem *prev )
543 {
544 Q_UNUSED( prev )
545
546 QgsDebugMsg( QStringLiteral( "Entered" ) );
547
548 const QString id = cur->data( Qt::UserRole ).toString();
549 if ( !id.isEmpty() )
550 {
551 setCurrentDefinedConfig( id );
552 }
553 }
554
555
selectCurrentDefinedConfig()556 void QgsAuthOAuth2Edit::selectCurrentDefinedConfig()
557 {
558 if ( mDefinedId.isEmpty() )
559 {
560 return;
561 }
562
563 if ( !onDefinedTab() )
564 {
565 tabConfigs->setCurrentIndex( definedTab() );
566 }
567
568 lstwdgDefinedConfigs->selectionModel()->clearSelection();
569
570 for ( int i = 0; i < lstwdgDefinedConfigs->count(); ++i )
571 {
572 QListWidgetItem *itm = lstwdgDefinedConfigs->item( i );
573
574 if ( itm->data( Qt::UserRole ).toString() == mDefinedId )
575 {
576 lstwdgDefinedConfigs->setCurrentItem( itm, QItemSelectionModel::Select );
577 break;
578 }
579 }
580 }
581
getDefinedCustomDir()582 void QgsAuthOAuth2Edit::getDefinedCustomDir()
583 {
584 const QString extradir = QFileDialog::getExistingDirectory( this, tr( "Select extra directory to parse" ),
585 QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks );
586 this->raise();
587 this->activateWindow();
588
589 if ( extradir.isEmpty() )
590 {
591 return;
592 }
593 leDefinedDirPath->setText( extradir );
594 }
595
getSoftStatementDir()596 void QgsAuthOAuth2Edit::getSoftStatementDir()
597 {
598 const QString softStatementFile = QFileDialog::getOpenFileName( this, tr( "Select software statement file" ),
599 QDir::homePath(), tr( "JSON Web Token (*.jwt)" ) );
600 this->raise();
601 this->activateWindow();
602
603 if ( softStatementFile.isEmpty() )
604 {
605 return;
606 }
607 leSoftwareStatementJwtPath->setText( softStatementFile );
608 }
609
initConfigObjs()610 void QgsAuthOAuth2Edit::initConfigObjs()
611 {
612 mOAuthConfigCustom = std::make_unique<QgsAuthOAuth2Config>( nullptr );
613 mOAuthConfigCustom->setConfigType( QgsAuthOAuth2Config::Custom );
614 mOAuthConfigCustom->setToDefaults();
615 }
616
617
hasTokenCacheFile()618 bool QgsAuthOAuth2Edit::hasTokenCacheFile()
619 {
620 const QString authcfg = parentConfigId();
621 if ( authcfg.isEmpty() )
622 {
623 QgsDebugMsg( QStringLiteral( "Auth config ID empty in ID widget of parent" ) );
624 return false;
625 }
626
627 return ( QFile::exists( QgsAuthOAuth2Config::tokenCachePath( authcfg, false ) )
628 || QFile::exists( QgsAuthOAuth2Config::tokenCachePath( authcfg, true ) ) );
629 }
630
631 //slot
removeTokenCacheFile()632 void QgsAuthOAuth2Edit::removeTokenCacheFile()
633 {
634 const QString authcfg = parentConfigId();
635 if ( authcfg.isEmpty() )
636 {
637 QgsDebugMsg( QStringLiteral( "Auth config ID empty in ID widget of parent" ) );
638 return;
639 }
640
641 const QStringList cachefiles = QStringList()
642 << QgsAuthOAuth2Config::tokenCachePath( authcfg, false )
643 << QgsAuthOAuth2Config::tokenCachePath( authcfg, true );
644
645 for ( const QString &cachefile : cachefiles )
646 {
647 if ( QFile::exists( cachefile ) && !QFile::remove( cachefile ) )
648 {
649 QgsDebugMsg( QStringLiteral( "Remove token cache file FAILED for authcfg %1: %2" ).arg( authcfg, cachefile ) );
650 }
651 }
652 btnTokenClear->setEnabled( hasTokenCacheFile() );
653 }
654
updateDefinedConfigsCache()655 void QgsAuthOAuth2Edit::updateDefinedConfigsCache()
656 {
657 const QString extradir = leDefinedDirPath->text();
658 mDefinedConfigsCache.clear();
659 mDefinedConfigsCache = QgsAuthOAuth2Config::mappedOAuth2ConfigsCache( this, extradir );
660 }
661
loadDefinedConfigs()662 void QgsAuthOAuth2Edit::loadDefinedConfigs()
663 {
664 whileBlocking( lstwdgDefinedConfigs )->clear();
665 updateDefinedConfigsCache();
666 updatePredefinedLocationsTooltip();
667
668 QgsStringMap::const_iterator i = mDefinedConfigsCache.constBegin();
669 while ( i != mDefinedConfigsCache.constEnd() )
670 {
671 QgsAuthOAuth2Config *config = new QgsAuthOAuth2Config( this );
672 if ( !config->loadConfigTxt( i.value().toUtf8(), QgsAuthOAuth2Config::JSON ) )
673 {
674 QgsDebugMsg( QStringLiteral( "FAILED to load config for ID: %1" ).arg( i.key() ) );
675 config->deleteLater();
676 continue;
677 }
678
679 const QString grantflow = QgsAuthOAuth2Config::grantFlowString( config->grantFlow() );
680
681 const QString name = QStringLiteral( "%1 (%2): %3" )
682 .arg( config->name(), grantflow, config->description() );
683
684 const QString tip = tr( "ID: %1\nGrant flow: %2\nDescription: %3" )
685 .arg( i.key(), grantflow, config->description() );
686
687 QListWidgetItem *itm = new QListWidgetItem( lstwdgDefinedConfigs );
688 itm->setText( name );
689 itm->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
690 itm->setData( Qt::UserRole, QVariant( i.key() ) );
691 itm->setData( Qt::ToolTipRole, QVariant( tip ) );
692 lstwdgDefinedConfigs->addItem( itm );
693
694 config->deleteLater();
695 ++i;
696 }
697
698 if ( lstwdgDefinedConfigs->count() == 0 )
699 {
700 QListWidgetItem *itm = new QListWidgetItem( lstwdgDefinedConfigs );
701 itm->setText( tr( "No predefined configurations found on disk" ) );
702 QFont f( itm->font() );
703 f.setItalic( true );
704 itm->setFont( f );
705 itm->setFlags( Qt::NoItemFlags );
706 lstwdgDefinedConfigs->addItem( itm );
707 }
708
709 selectCurrentDefinedConfig();
710 }
711
onCustomTab() const712 bool QgsAuthOAuth2Edit::onCustomTab() const
713 {
714 return mCurTab == customTab();
715 }
716
onDefinedTab() const717 bool QgsAuthOAuth2Edit::onDefinedTab() const
718 {
719 return mCurTab == definedTab();
720 }
721
onStatementTab() const722 bool QgsAuthOAuth2Edit::onStatementTab() const
723 {
724 return mCurTab == statementTab();
725 }
726
updateGrantFlow(int indx)727 void QgsAuthOAuth2Edit::updateGrantFlow( int indx )
728 {
729 if ( cmbbxGrantFlow->currentIndex() != indx )
730 {
731 whileBlocking( cmbbxGrantFlow )->setCurrentIndex( indx );
732 }
733
734 const QgsAuthOAuth2Config::GrantFlow flow =
735 static_cast<QgsAuthOAuth2Config::GrantFlow>( cmbbxGrantFlow->itemData( indx ).toInt() );
736 mOAuthConfigCustom->setGrantFlow( flow );
737
738 // bool authcode = ( flow == QgsAuthOAuth2Config::AuthCode );
739 const bool implicit = ( flow == QgsAuthOAuth2Config::Implicit );
740 const bool resowner = ( flow == QgsAuthOAuth2Config::ResourceOwner );
741
742 lblRequestUrl->setVisible( !resowner );
743 leRequestUrl->setVisible( !resowner );
744 if ( resowner )
745 leRequestUrl->setText( QString() );
746
747 lblRedirectUrl->setVisible( !resowner );
748 frameRedirectUrl->setVisible( !resowner );
749
750 lblClientSecret->setVisible( !implicit );
751 leClientSecret->setVisible( !implicit );
752 if ( implicit )
753 leClientSecret->setText( QString() );
754
755 leClientId->setPlaceholderText( resowner ? tr( "Optional" ) : tr( "Required" ) );
756 leClientSecret->setPlaceholderText( resowner ? tr( "Optional" ) : tr( "Required" ) );
757
758
759 lblUsername->setVisible( resowner );
760 leUsername->setVisible( resowner );
761 if ( !resowner )
762 leUsername->setText( QString() );
763 lblPassword->setVisible( resowner );
764 lePassword->setVisible( resowner );
765 if ( !resowner )
766 lePassword->setText( QString() );
767 }
768
769
exportOAuthConfig()770 void QgsAuthOAuth2Edit::exportOAuthConfig()
771 {
772 if ( !onCustomTab() || !mValid )
773 {
774 return;
775 }
776
777 QSettings settings;
778 const QString recentdir = settings.value( QStringLiteral( "UI/lastAuthSaveFileDir" ), QDir::homePath() ).toString();
779 const QString configpath = QFileDialog::getSaveFileName(
780 this, tr( "Save OAuth2 Config File" ), recentdir, QStringLiteral( "OAuth2 config files (*.json)" ) );
781 this->raise();
782 this->activateWindow();
783
784 if ( configpath.isEmpty() )
785 {
786 return;
787 }
788 settings.setValue( QStringLiteral( "UI/lastAuthSaveFileDir" ), QFileInfo( configpath ).absoluteDir().path() );
789
790 // give it a kind of random id for re-importing
791 mOAuthConfigCustom->setId( QgsApplication::authManager()->uniqueConfigId() );
792
793 mOAuthConfigCustom->setQueryPairs( queryPairs() );
794
795 if ( mParentName && !mParentName->text().isEmpty() )
796 {
797 mOAuthConfigCustom->setName( mParentName->text() );
798 }
799
800 if ( !QgsAuthOAuth2Config::writeOAuth2Config( configpath, mOAuthConfigCustom.get(),
801 QgsAuthOAuth2Config::JSON, true ) )
802 {
803 QgsDebugMsg( QStringLiteral( "FAILED to export OAuth2 config file" ) );
804 }
805 // clear temp changes
806 mOAuthConfigCustom->setId( QString() );
807 mOAuthConfigCustom->setName( QString() );
808 }
809
810
importOAuthConfig()811 void QgsAuthOAuth2Edit::importOAuthConfig()
812 {
813 if ( !onCustomTab() )
814 {
815 return;
816 }
817
818 const QString configfile =
819 QgsAuthGuiUtils::getOpenFileName( this, tr( "Select OAuth2 Config File" ), QStringLiteral( "OAuth2 config files (*.json)" ) );
820 this->raise();
821 this->activateWindow();
822
823 const QFileInfo importinfo( configfile );
824 if ( configfile.isEmpty() || !importinfo.exists() )
825 {
826 return;
827 }
828
829 QByteArray configtxt;
830 QFile cfile( configfile );
831 const bool ret = cfile.open( QIODevice::ReadOnly | QIODevice::Text );
832 if ( ret )
833 {
834 configtxt = cfile.readAll();
835 }
836 else
837 {
838 QgsDebugMsg( QStringLiteral( "FAILED to open config for reading: %1" ).arg( configfile ) );
839 cfile.close();
840 return;
841 }
842 cfile.close();
843
844 if ( configtxt.isEmpty() )
845 {
846 QgsDebugMsg( QStringLiteral( "EMPTY read of config: %1" ).arg( configfile ) );
847 return;
848 }
849
850 QgsStringMap configmap;
851 configmap.insert( QStringLiteral( "oauth2config" ), QString( configtxt ) );
852 loadConfig( configmap );
853 }
854
855
descriptionChanged()856 void QgsAuthOAuth2Edit::descriptionChanged()
857 {
858 mOAuthConfigCustom->setDescription( pteDescription->toPlainText() );
859 }
860
861
populateAccessMethods()862 void QgsAuthOAuth2Edit::populateAccessMethods()
863 {
864 cmbbxAccessMethod->addItem( QgsAuthOAuth2Config::accessMethodString( QgsAuthOAuth2Config::Header ),
865 static_cast<int>( QgsAuthOAuth2Config::Header ) );
866 cmbbxAccessMethod->addItem( QgsAuthOAuth2Config::accessMethodString( QgsAuthOAuth2Config::Form ),
867 static_cast<int>( QgsAuthOAuth2Config::Form ) );
868 cmbbxAccessMethod->addItem( QgsAuthOAuth2Config::accessMethodString( QgsAuthOAuth2Config::Query ),
869 static_cast<int>( QgsAuthOAuth2Config::Query ) );
870 }
871
872
updateConfigAccessMethod(int indx)873 void QgsAuthOAuth2Edit::updateConfigAccessMethod( int indx )
874 {
875 mOAuthConfigCustom->setAccessMethod( static_cast<QgsAuthOAuth2Config::AccessMethod>( indx ) );
876 switch ( static_cast<QgsAuthOAuth2Config::AccessMethod>( indx ) )
877 {
878 case QgsAuthOAuth2Config::Header:
879 mTokenHeaderLineEdit->setVisible( true );
880 mTokenHeaderLabel->setVisible( true );
881 break;
882 case QgsAuthOAuth2Config::Form:
883 case QgsAuthOAuth2Config::Query:
884 mTokenHeaderLineEdit->setVisible( false );
885 mTokenHeaderLabel->setVisible( false );
886 break;
887 }
888 }
889
addQueryPairRow(const QString & key,const QString & val)890 void QgsAuthOAuth2Edit::addQueryPairRow( const QString &key, const QString &val )
891 {
892 const int rowCnt = tblwdgQueryPairs->rowCount();
893 tblwdgQueryPairs->insertRow( rowCnt );
894
895 const Qt::ItemFlags itmFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable
896 | Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
897
898 QTableWidgetItem *keyItm = new QTableWidgetItem( key );
899 keyItm->setFlags( itmFlags );
900 tblwdgQueryPairs->setItem( rowCnt, 0, keyItm );
901
902 QTableWidgetItem *valItm = new QTableWidgetItem( val );
903 keyItm->setFlags( itmFlags );
904 tblwdgQueryPairs->setItem( rowCnt, 1, valItm );
905 }
906
907
populateQueryPairs(const QVariantMap & querypairs,bool append)908 void QgsAuthOAuth2Edit::populateQueryPairs( const QVariantMap &querypairs, bool append )
909 {
910 if ( !append )
911 {
912 clearQueryPairs();
913 }
914
915 QVariantMap::const_iterator i = querypairs.constBegin();
916 while ( i != querypairs.constEnd() )
917 {
918 addQueryPairRow( i.key(), i.value().toString() );
919 ++i;
920 }
921 }
922
923
queryTableSelectionChanged()924 void QgsAuthOAuth2Edit::queryTableSelectionChanged()
925 {
926 const bool hassel = tblwdgQueryPairs->selectedItems().count() > 0;
927 btnRemoveQueryPair->setEnabled( hassel );
928 }
929
updateConfigQueryPairs()930 void QgsAuthOAuth2Edit::updateConfigQueryPairs()
931 {
932 mOAuthConfigCustom->setQueryPairs( queryPairs() );
933 }
934
queryPairs() const935 QVariantMap QgsAuthOAuth2Edit::queryPairs() const
936 {
937 QVariantMap querypairs;
938 for ( int i = 0; i < tblwdgQueryPairs->rowCount(); ++i )
939 {
940 if ( tblwdgQueryPairs->item( i, 0 )->text().isEmpty() )
941 {
942 continue;
943 }
944 querypairs.insert( tblwdgQueryPairs->item( i, 0 )->text(),
945 QVariant( tblwdgQueryPairs->item( i, 1 )->text() ) );
946 }
947 return querypairs;
948 }
949
950
addQueryPair()951 void QgsAuthOAuth2Edit::addQueryPair()
952 {
953 addQueryPairRow( QString(), QString() );
954 tblwdgQueryPairs->setFocus();
955 tblwdgQueryPairs->setCurrentCell( tblwdgQueryPairs->rowCount() - 1, 0 );
956 tblwdgQueryPairs->edit( tblwdgQueryPairs->currentIndex() );
957 }
958
959
removeQueryPair()960 void QgsAuthOAuth2Edit::removeQueryPair()
961 {
962 tblwdgQueryPairs->removeRow( tblwdgQueryPairs->currentRow() );
963 }
964
965
clearQueryPairs()966 void QgsAuthOAuth2Edit::clearQueryPairs()
967 {
968 for ( int i = tblwdgQueryPairs->rowCount(); i > 0 ; --i )
969 {
970 tblwdgQueryPairs->removeRow( i - 1 );
971 }
972 }
973
parseSoftwareStatement(const QString & path)974 void QgsAuthOAuth2Edit::parseSoftwareStatement( const QString &path )
975 {
976 QFile file( path );
977 QByteArray softwareStatementBase64;
978 if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
979 {
980 softwareStatementBase64 = file.readAll();
981 }
982 if ( softwareStatementBase64.isEmpty() )
983 {
984 QgsDebugMsg( QStringLiteral( "Error software statement is empty: %1" ).arg( path ) );
985 file.close();
986 return;
987 }
988 mRegistrationEndpoint = QString();
989 file.close();
990 mSoftwareStatement.insert( QStringLiteral( "software_statement" ), softwareStatementBase64 );
991 QList<QByteArray> payloadParts( softwareStatementBase64.split( '.' ) );
992 if ( payloadParts.count() < 2 )
993 {
994 QgsDebugMsg( QStringLiteral( "Error parsing JSON: base64 decode returned less than 2 parts" ) );
995 return;
996 }
997 const QByteArray payload = payloadParts[1];
998 QByteArray decoded = QByteArray::fromBase64( payload/*, QByteArray::Base64UrlEncoding*/ );
999 QByteArray errStr;
1000 bool res = false;
1001 const QMap<QString, QVariant> jsonData = QJsonWrapper::parseJson( decoded, &res, &errStr ).toMap();
1002 if ( !res )
1003 {
1004 QgsDebugMsg( QStringLiteral( "Error parsing JSON: %1" ).arg( QString( errStr ) ) );
1005 return;
1006 }
1007 if ( jsonData.contains( QStringLiteral( "grant_types" ) ) && jsonData.contains( QStringLiteral( "redirect_uris" ) ) )
1008 {
1009 const QStringList grantTypes( jsonData[QStringLiteral( "grant_types" ) ].toStringList() );
1010 if ( !grantTypes.isEmpty( ) )
1011 {
1012 const QString grantType = grantTypes[0];
1013 if ( grantType == QLatin1String( "authorization_code" ) )
1014 {
1015 updateGrantFlow( static_cast<int>( QgsAuthOAuth2Config::AuthCode ) );
1016 }
1017 else
1018 {
1019 updateGrantFlow( static_cast<int>( QgsAuthOAuth2Config::ResourceOwner ) );
1020 }
1021 }
1022 //Set redirect_uri
1023 const QStringList redirectUris( jsonData[QStringLiteral( "redirect_uris" ) ].toStringList() );
1024 if ( !redirectUris.isEmpty( ) )
1025 {
1026 const QString redirectUri = redirectUris[0];
1027 leRedirectUrl->setText( redirectUri );
1028 }
1029 }
1030 else
1031 {
1032 QgsDebugMsgLevel( QStringLiteral( "Error software statement is invalid: %1" ).arg( path ), 4 );
1033 return;
1034 }
1035 if ( jsonData.contains( QStringLiteral( "registration_endpoint" ) ) )
1036 {
1037 mRegistrationEndpoint = jsonData[QStringLiteral( "registration_endpoint" )].toString();
1038 leSoftwareStatementConfigUrl->setText( mRegistrationEndpoint );
1039 }
1040 QgsDebugMsgLevel( QStringLiteral( "JSON: %1" ).arg( QString::fromLocal8Bit( decoded.data() ) ), 4 );
1041 }
1042
configReplyFinished()1043 void QgsAuthOAuth2Edit::configReplyFinished()
1044 {
1045 qDebug() << "QgsAuthOAuth2Edit::onConfigReplyFinished";
1046 QNetworkReply *configReply = qobject_cast<QNetworkReply *>( sender() );
1047 if ( configReply->error() == QNetworkReply::NoError )
1048 {
1049 const QByteArray replyData = configReply->readAll();
1050 QByteArray errStr;
1051 bool res = false;
1052 const QVariantMap config = QJsonWrapper::parseJson( replyData, &res, &errStr ).toMap();
1053
1054 if ( !res )
1055 {
1056 QgsDebugMsg( QStringLiteral( "Error parsing JSON: %1" ).arg( QString( errStr ) ) );
1057 return;
1058 }
1059 // I haven't found any docs about the content of this confg JSON file
1060 // I assume that registration_endpoint is all that it MUST contain.
1061 // But we also MAY have other optional information here
1062 if ( config.contains( QStringLiteral( "registration_endpoint" ) ) )
1063 {
1064 if ( config.contains( QStringLiteral( "authorization_endpoint" ) ) )
1065 leRequestUrl->setText( config.value( QStringLiteral( "authorization_endpoint" ) ).toString() );
1066 if ( config.contains( QStringLiteral( "token_endpoint" ) ) )
1067 leTokenUrl->setText( config.value( QStringLiteral( "token_endpoint" ) ).toString() );
1068
1069 registerSoftStatement( config.value( QStringLiteral( "registration_endpoint" ) ).toString() );
1070 }
1071 else
1072 {
1073 const QString errorMsg = tr( "Downloading configuration failed with error: %1" ).arg( configReply->errorString() );
1074 QgsMessageLog::logMessage( errorMsg, QStringLiteral( "OAuth2" ), Qgis::MessageLevel::Critical );
1075 }
1076 }
1077 mDownloading = false;
1078 configReply->deleteLater();
1079 }
1080
registerReplyFinished()1081 void QgsAuthOAuth2Edit::registerReplyFinished()
1082 {
1083 //JSV todo
1084 //better error handling
1085 qDebug() << "QgsAuthOAuth2Edit::onRegisterReplyFinished";
1086 QNetworkReply *registerReply = qobject_cast<QNetworkReply *>( sender() );
1087 if ( registerReply->error() == QNetworkReply::NoError )
1088 {
1089 const QByteArray replyData = registerReply->readAll();
1090 QByteArray errStr;
1091 bool res = false;
1092 const QVariantMap clientInfo = QJsonWrapper::parseJson( replyData, &res, &errStr ).toMap();
1093
1094 // According to RFC 7591 sec. 3.2.1. Client Information Response the only
1095 // required field is client_id
1096 leClientId->setText( clientInfo.value( QStringLiteral( "client_id" ) ).toString() );
1097 if ( clientInfo.contains( QStringLiteral( "client_secret" ) ) )
1098 leClientSecret->setText( clientInfo.value( QStringLiteral( "client_secret" ) ).toString() );
1099 if ( clientInfo.contains( QStringLiteral( "authorization_endpoint" ) ) )
1100 leRequestUrl->setText( clientInfo.value( QStringLiteral( "authorization_endpoint" ) ).toString() );
1101 if ( clientInfo.contains( QStringLiteral( "token_endpoint" ) ) )
1102 leTokenUrl->setText( clientInfo.value( QStringLiteral( "token_endpoint" ) ).toString() );
1103 if ( clientInfo.contains( QStringLiteral( "scopes" ) ) )
1104 leScope->setText( clientInfo.value( QStringLiteral( "scopes" ) ).toString() );
1105
1106 tabConfigs->setCurrentIndex( 0 );
1107 }
1108 else
1109 {
1110 const QString errorMsg = QStringLiteral( "Client registration failed with error: %1" ).arg( registerReply->errorString() );
1111 QgsMessageLog::logMessage( errorMsg, QStringLiteral( "OAuth2" ), Qgis::MessageLevel::Critical );
1112 }
1113 mDownloading = false;
1114 registerReply->deleteLater();
1115 }
1116
networkError(QNetworkReply::NetworkError error)1117 void QgsAuthOAuth2Edit::networkError( QNetworkReply::NetworkError error )
1118 {
1119 QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
1120 qWarning() << "QgsAuthOAuth2Edit::onNetworkError: " << error << ": " << reply->errorString();
1121 const QString errorMsg = QStringLiteral( "Network error: %1" ).arg( reply->errorString() );
1122 QgsMessageLog::logMessage( errorMsg, QStringLiteral( "OAuth2" ), Qgis::MessageLevel::Critical );
1123 qDebug() << "QgsAuthOAuth2Edit::onNetworkError: " << reply->readAll();
1124 }
1125
1126
registerSoftStatement(const QString & registrationUrl)1127 void QgsAuthOAuth2Edit::registerSoftStatement( const QString ®istrationUrl )
1128 {
1129 const QUrl regUrl( registrationUrl );
1130 if ( !regUrl.isValid() )
1131 {
1132 qWarning() << "Registration url is not valid";
1133 return;
1134 }
1135 QByteArray errStr;
1136 bool res = false;
1137 const QByteArray json = QJsonWrapper::toJson( QVariant( mSoftwareStatement ), &res, &errStr );
1138 QNetworkRequest registerRequest( regUrl );
1139 QgsSetRequestInitiatorClass( registerRequest, QStringLiteral( "QgsAuthOAuth2Edit" ) );
1140 registerRequest.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1String( "application/json" ) );
1141 QNetworkReply *registerReply;
1142 // For testability: use GET if protocol is file://
1143 if ( regUrl.scheme() == QLatin1String( "file" ) )
1144 registerReply = QgsNetworkAccessManager::instance()->get( registerRequest );
1145 else
1146 registerReply = QgsNetworkAccessManager::instance()->post( registerRequest, json );
1147 mDownloading = true;
1148 connect( registerReply, &QNetworkReply::finished, this, &QgsAuthOAuth2Edit::registerReplyFinished, Qt::QueuedConnection );
1149 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1150 connect( registerReply, qOverload<QNetworkReply::NetworkError>( &QNetworkReply::error ), this, &QgsAuthOAuth2Edit::networkError, Qt::QueuedConnection );
1151 #else
1152 connect( registerReply, &QNetworkReply::errorOccurred, this, &QgsAuthOAuth2Edit::networkError, Qt::QueuedConnection );
1153 #endif
1154 }
1155
getSoftwareStatementConfig()1156 void QgsAuthOAuth2Edit::getSoftwareStatementConfig()
1157 {
1158 if ( !mRegistrationEndpoint.isEmpty() )
1159 {
1160 registerSoftStatement( mRegistrationEndpoint );
1161 }
1162 else
1163 {
1164 const QString config = leSoftwareStatementConfigUrl->text();
1165 const QUrl configUrl( config );
1166 QNetworkRequest configRequest( configUrl );
1167 QgsSetRequestInitiatorClass( configRequest, QStringLiteral( "QgsAuthOAuth2Edit" ) );
1168 QNetworkReply *configReply = QgsNetworkAccessManager::instance()->get( configRequest );
1169 mDownloading = true;
1170 connect( configReply, &QNetworkReply::finished, this, &QgsAuthOAuth2Edit::configReplyFinished, Qt::QueuedConnection );
1171 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1172 connect( configReply, qOverload<QNetworkReply::NetworkError>( &QNetworkReply::error ), this, &QgsAuthOAuth2Edit::networkError, Qt::QueuedConnection );
1173 #else
1174 connect( configReply, &QNetworkReply::errorOccurred, this, &QgsAuthOAuth2Edit::networkError, Qt::QueuedConnection );
1175 #endif
1176 }
1177 }
1178
updatePredefinedLocationsTooltip()1179 void QgsAuthOAuth2Edit::updatePredefinedLocationsTooltip()
1180 {
1181 const QStringList dirs = QgsAuthOAuth2Config::configLocations( leDefinedDirPath->text() );
1182 QString locationList;
1183 QString locationListHtml;
1184 for ( const QString &dir : dirs )
1185 {
1186 if ( !locationList.isEmpty() )
1187 locationList += '\n';
1188 if ( locationListHtml.isEmpty() )
1189 locationListHtml = QStringLiteral( "<ul>" );
1190 locationList += QStringLiteral( "• %1" ).arg( dir );
1191 locationListHtml += QStringLiteral( "<li><a href=\"%1\">%2</a></li>" ).arg( QUrl::fromLocalFile( dir ).toString(), dir );
1192 }
1193 if ( !locationListHtml.isEmpty() )
1194 locationListHtml += QLatin1String( "</ul>" );
1195
1196 const QString tip = QStringLiteral( "<p>" ) + tr( "Defined configurations are JSON-formatted files, with a single configuration per file. "
1197 "This allows configurations to be swapped out via filesystem tools without affecting user "
1198 "configurations. It is recommended to use the Configure tab’s export function, then edit the "
1199 "resulting file. See QGIS documentation for further details." ) + QStringLiteral( "</p><p>" ) +
1200 tr( "Configurations files can be placed in the directories:" ) + QStringLiteral( "</p>" ) + locationListHtml;
1201 pteDefinedDesc->setHtml( tip );
1202
1203 lstwdgDefinedConfigs->setToolTip( tr( "Configuration files can be placed in the directories:\n\n%1" ).arg( locationList ) );
1204 }
1205
1206