PageRenderTime 45ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/src/auth/oauth2/qgsauthoauth2edit.cpp

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