QGIS/src/auth/oauth2/qgsauthoauth2edit.cpp
Nyall Dawson ca06d407a0 Add custom QNetworkRequest::Attributes for initiator network request class name and internal id
And allow these to be retrieved from QgsNetworkRequestParameters.
This allows logging code to identify the area of code where a request
originated from, making debugging much easier!

Tag all requests created with appropriate class name and IDs
2019-01-25 23:47:05 +11:00

1181 lines
40 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/***************************************************************************
begin : July 13, 2016
copyright : (C) 2016 by Monsanto Company, USA
author : Larry Shaffer, Boundless Spatial
email : lshaffer at boundlessgeo dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsauthoauth2edit.h"
#include "ui_qgsauthoauth2edit.h"
#include <QDir>
#include <QFileDialog>
#include <QDesktopServices>
#include "qgsapplication.h"
#include "qgsauthguiutils.h"
#include "qgsauthmanager.h"
#include "qgsauthconfigedit.h"
#include "qgsmessagelog.h"
#include "qgsnetworkaccessmanager.h"
#include "qjsonwrapper/Json.h"
QgsAuthOAuth2Edit::QgsAuthOAuth2Edit( QWidget *parent )
: QgsAuthMethodEdit( parent )
, mDefinedConfigsCache( QgsStringMap() )
{
setupUi( this );
initGui();
initConfigObjs();
populateGrantFlows();
updateGrantFlow( static_cast<int>( QgsAuthOAuth2Config::AuthCode ) ); // first index: Authorization Code
populateAccessMethods();
queryTableSelectionChanged();
loadDefinedConfigs();
setupConnections();
loadFromOAuthConfig( mOAuthConfigCustom.get() );
updatePredefinedLocationsTooltip();
pteDefinedDesc->setOpenLinks( false );
connect( pteDefinedDesc, &QTextBrowser::anchorClicked, this, [ = ]( const QUrl & url )
{
QDesktopServices::openUrl( url );
} );
}
void QgsAuthOAuth2Edit::initGui()
{
mParentName = parentNameField();
frameNotify->setVisible( false );
// TODO: add messagebar to notify frame?
tabConfigs->setCurrentIndex( customTab() );
btnExport->setEnabled( false );
chkbxTokenPersist->setChecked( false );
grpbxAdvanced->setCollapsed( true );
grpbxAdvanced->setFlat( false );
btnTokenClear = new QToolButton( this );
btnTokenClear->setObjectName( QStringLiteral( "btnTokenClear" ) );
btnTokenClear->setMaximumHeight( 20 );
btnTokenClear->setText( tr( "Tokens" ) );
btnTokenClear->setToolTip( tr( "Remove cached tokens" ) );
btnTokenClear->setIcon( QIcon( QStringLiteral( ":/oauth2method/oauth2_resources/close.svg" ) ) );
btnTokenClear->setIconSize( QSize( 12, 12 ) );
btnTokenClear->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
btnTokenClear->setEnabled( hasTokenCacheFile() );
connect( btnTokenClear, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::removeTokenCacheFile );
tabConfigs->setCornerWidget( btnTokenClear, Qt::TopRightCorner );
}
QWidget *QgsAuthOAuth2Edit::parentWidget() const
{
if ( !window() )
{
return nullptr;
}
const QMetaObject *metaObject = window()->metaObject();
QString parentclass = metaObject->className();
//QgsDebugMsg( QStringLiteral( "parent class: %1" ).arg( parentclass ) );
if ( parentclass != QStringLiteral( "QgsAuthConfigEdit" ) )
{
QgsDebugMsg( QStringLiteral( "Parent widget not QgsAuthConfigEdit instance" ) );
return nullptr;
}
return window();
}
QLineEdit *QgsAuthOAuth2Edit::parentNameField() const
{
return parentWidget() ? parentWidget()->findChild<QLineEdit *>( QStringLiteral( "leName" ) ) : nullptr;
}
QString QgsAuthOAuth2Edit::parentConfigId() const
{
if ( !parentWidget() )
{
return QString();
}
QgsAuthConfigEdit *cie = qobject_cast<QgsAuthConfigEdit *>( parentWidget() );
if ( !cie )
{
QgsDebugMsg( QStringLiteral( "Could not cast to QgsAuthConfigEdit" ) );
return QString();
}
if ( cie->configId().isEmpty() )
{
QgsDebugMsg( QStringLiteral( "QgsAuthConfigEdit->configId() is empty" ) );
}
return cie->configId();
}
void QgsAuthOAuth2Edit::setupConnections()
{
// Action and interaction connections
connect( tabConfigs, &QTabWidget::currentChanged, this, &QgsAuthOAuth2Edit::tabIndexChanged );
connect( btnExport, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::exportOAuthConfig );
connect( btnImport, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::importOAuthConfig );
connect( tblwdgQueryPairs, &QTableWidget::itemSelectionChanged, this, &QgsAuthOAuth2Edit::queryTableSelectionChanged );
connect( btnAddQueryPair, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::addQueryPair );
connect( btnRemoveQueryPair, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::removeQueryPair );
connect( lstwdgDefinedConfigs, &QListWidget::currentItemChanged, this, &QgsAuthOAuth2Edit::currentDefinedItemChanged );
connect( btnGetDefinedDirPath, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::getDefinedCustomDir );
connect( leDefinedDirPath, &QLineEdit::textChanged, this, &QgsAuthOAuth2Edit::definedCustomDirChanged );
connect( btnSoftStatementDir, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::getSoftStatementDir );
connect( leSoftwareStatementJwtPath, &QLineEdit::textChanged, this, &QgsAuthOAuth2Edit::softwareStatementJwtPathChanged );
connect( leSoftwareStatementConfigUrl, &QLineEdit::textChanged, this, [ = ]( const QString & txt )
{
btnRegister->setEnabled( ! leSoftwareStatementJwtPath->text().isEmpty()
&& ( QUrl( txt ).isValid() || ! mRegistrationEndpoint.isEmpty() ) );
} );
connect( btnRegister, &QPushButton::clicked, this, &QgsAuthOAuth2Edit::getSoftwareStatementConfig );
// Custom config editing connections
connect( cmbbxGrantFlow, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
this, &QgsAuthOAuth2Edit::updateGrantFlow ); // also updates GUI
connect( pteDescription, &QPlainTextEdit::textChanged, this, &QgsAuthOAuth2Edit::descriptionChanged );
connect( leRequestUrl, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRequestUrl );
connect( leTokenUrl, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setTokenUrl );
connect( leRefreshTokenUrl, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRefreshTokenUrl );
connect( leRedirectUrl, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRedirectUrl );
connect( spnbxRedirectPort, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ),
mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRedirectPort );
connect( leClientId, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setClientId );
connect( leClientSecret, &QgsPasswordLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setClientSecret );
connect( leUsername, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setUsername );
connect( lePassword, &QgsPasswordLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setPassword );
connect( leScope, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setScope );
connect( leApiKey, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setApiKey );
connect( chkbxTokenPersist, &QCheckBox::toggled, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setPersistToken );
connect( cmbbxAccessMethod, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
this, &QgsAuthOAuth2Edit::updateConfigAccessMethod );
connect( spnbxRequestTimeout, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ),
mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRequestTimeout );
connect( mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::validityChanged, this, &QgsAuthOAuth2Edit::configValidityChanged );
if ( mParentName )
{
connect( mParentName, &QLineEdit::textChanged, this, &QgsAuthOAuth2Edit::configValidityChanged );
}
}
void QgsAuthOAuth2Edit::configValidityChanged()
{
validateConfig();
bool parentname = mParentName && !mParentName->text().isEmpty();
btnExport->setEnabled( mValid && parentname );
}
bool QgsAuthOAuth2Edit::validateConfig()
{
bool curvalid = ( onCustomTab() ? mOAuthConfigCustom->isValid() : !mDefinedId.isEmpty() );
if ( mValid != curvalid )
{
mValid = curvalid;
emit validityChanged( curvalid );
}
return curvalid;
}
QgsStringMap QgsAuthOAuth2Edit::configMap() const
{
QgsStringMap configmap;
bool ok = false;
if ( onCustomTab() )
{
if ( !mOAuthConfigCustom || !mOAuthConfigCustom->isValid() )
{
QgsDebugMsg( QStringLiteral( "FAILED to serialize OAuth config object: null or invalid object" ) );
return configmap;
}
mOAuthConfigCustom->setQueryPairs( queryPairs() );
QByteArray configtxt = mOAuthConfigCustom->saveConfigTxt( QgsAuthOAuth2Config::JSON, false, &ok );
if ( !ok )
{
QgsDebugMsg( QStringLiteral( "FAILED to serialize OAuth config object" ) );
return configmap;
}
if ( configtxt.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "FAILED to serialize OAuth config object: content empty" ) );
return configmap;
}
//###################### DO NOT LEAVE ME UNCOMMENTED #####################
//QgsDebugMsg( QStringLiteral( "SAVE oauth2config configtxt: \n\n%1\n\n" ).arg( QString( configtxt ) ) );
//###################### DO NOT LEAVE ME UNCOMMENTED #####################
configmap.insert( QStringLiteral( "oauth2config" ), QString( configtxt ) );
updateTokenCacheFile( mOAuthConfigCustom->persistToken() );
}
else if ( onDefinedTab() && !mDefinedId.isEmpty() )
{
configmap.insert( QStringLiteral( "definedid" ), mDefinedId );
configmap.insert( QStringLiteral( "defineddirpath" ), leDefinedDirPath->text() );
configmap.insert( QStringLiteral( "querypairs" ),
QgsAuthOAuth2Config::serializeFromVariant(
queryPairs(), QgsAuthOAuth2Config::JSON, false ) );
}
return configmap;
}
void QgsAuthOAuth2Edit::loadConfig( const QgsStringMap &configmap )
{
clearConfig();
mConfigMap = configmap;
bool ok = false;
//QgsDebugMsg( QStringLiteral( "oauth2config: " ).arg( configmap.value( QStringLiteral( "oauth2config" ) ) ) );
if ( configmap.contains( QStringLiteral( "oauth2config" ) ) )
{
tabConfigs->setCurrentIndex( customTab() );
QByteArray configtxt = configmap.value( QStringLiteral( "oauth2config" ) ).toUtf8();
if ( !configtxt.isEmpty() )
{
//###################### DO NOT LEAVE ME UNCOMMENTED #####################
//QgsDebugMsg( QStringLiteral( "LOAD oauth2config configtxt: \n\n%1\n\n" ).arg( QString( configtxt ) ) );
//###################### DO NOT LEAVE ME UNCOMMENTED #####################
if ( !mOAuthConfigCustom->loadConfigTxt( configtxt, QgsAuthOAuth2Config::JSON ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to load OAuth2 config into object" ) );
}
//###################### DO NOT LEAVE ME UNCOMMENTED #####################
//QVariantMap vmap = mOAuthConfigCustom->mappedProperties();
//QByteArray vmaptxt = QgsAuthOAuth2Config::serializeFromVariant(vmap, QgsAuthOAuth2Config::JSON, true );
//QgsDebugMsg( QStringLiteral( "LOAD oauth2config vmaptxt: \n\n%1\n\n" ).arg( QString( vmaptxt ) ) );
//###################### DO NOT LEAVE ME UNCOMMENTED #####################
// could only be loading defaults at this point
loadFromOAuthConfig( mOAuthConfigCustom.get() );
mPrevPersistToken = mOAuthConfigCustom->persistToken();
}
else
{
QgsDebugMsg( QStringLiteral( "FAILED to load OAuth2 config: empty config txt" ) );
}
}
else if ( configmap.contains( QStringLiteral( "definedid" ) ) )
{
tabConfigs->setCurrentIndex( definedTab() );
QString definedid = configmap.value( QStringLiteral( "definedid" ) );
setCurrentDefinedConfig( definedid );
if ( !definedid.isEmpty() )
{
if ( !configmap.value( QStringLiteral( "defineddirpath" ) ).isEmpty() )
{
// this will trigger a reload of dirs and a reselection of any existing defined id
leDefinedDirPath->setText( configmap.value( QStringLiteral( "defineddirpath" ) ) );
}
else
{
QgsDebugMsg( QStringLiteral( "No custom defined dir path to load OAuth2 config" ) );
selectCurrentDefinedConfig();
}
QByteArray querypairstxt = configmap.value( QStringLiteral( "querypairs" ) ).toUtf8();
if ( !querypairstxt.isNull() && !querypairstxt.isEmpty() )
{
QVariantMap querypairsmap =
QgsAuthOAuth2Config::variantFromSerialized( querypairstxt, QgsAuthOAuth2Config::JSON, &ok );
if ( ok )
{
populateQueryPairs( querypairsmap );
}
else
{
QgsDebugMsg( QStringLiteral( "No query pairs to load OAuth2 config: failed to parse" ) );
}
}
else
{
QgsDebugMsg( QStringLiteral( "No query pairs to load OAuth2 config: empty text" ) );
}
}
else
{
QgsDebugMsg( QStringLiteral( "FAILED to load a defined ID for OAuth2 config" ) );
}
}
validateConfig();
}
void QgsAuthOAuth2Edit::resetConfig()
{
loadConfig( mConfigMap );
}
void QgsAuthOAuth2Edit::clearConfig()
{
// restore defaults to config objs
mOAuthConfigCustom->setToDefaults();
mDefinedId.clear();
clearQueryPairs();
// clear any set predefined location
leDefinedDirPath->clear();
// reload predefined table
loadDefinedConfigs();
loadFromOAuthConfig( mOAuthConfigCustom.get() );
}
void QgsAuthOAuth2Edit::loadFromOAuthConfig( const QgsAuthOAuth2Config *config )
{
if ( !config )
{
return;
}
// load relative to config type
if ( config->configType() == QgsAuthOAuth2Config::Custom )
{
if ( config->isValid() )
{
tabConfigs->setCurrentIndex( customTab() );
}
pteDescription->setPlainText( config->description() );
leRequestUrl->setText( config->requestUrl() );
leTokenUrl->setText( config->tokenUrl() );
leRefreshTokenUrl->setText( config->refreshTokenUrl() );
leRedirectUrl->setText( config->redirectUrl() );
spnbxRedirectPort->setValue( config->redirectPort() );
leClientId->setText( config->clientId() );
leClientSecret->setText( config->clientSecret() );
leUsername->setText( config->username() );
lePassword->setText( config->password() );
leScope->setText( config->scope() );
leApiKey->setText( config->apiKey() );
// advanced
chkbxTokenPersist->setChecked( config->persistToken() );
cmbbxAccessMethod->setCurrentIndex( static_cast<int>( config->accessMethod() ) );
spnbxRequestTimeout->setValue( config->requestTimeout() );
populateQueryPairs( config->queryPairs() );
updateGrantFlow( static_cast<int>( config->grantFlow() ) );
}
validateConfig();
}
void QgsAuthOAuth2Edit::updateTokenCacheFile( bool curpersist ) const
{
// default for unset persistToken in config and edit GUI is false
if ( mPrevPersistToken == curpersist )
{
return;
}
if ( !parent() )
{
QgsDebugMsg( QStringLiteral( "Edit widget has no parent" ) );
return;
}
QString authcfg = parentConfigId();
if ( authcfg.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Auth config ID empty in ID widget of parent" ) );
return;
}
QString localcachefile = QgsAuthOAuth2Config::tokenCachePath( authcfg, false );
QString tempcachefile = QgsAuthOAuth2Config::tokenCachePath( authcfg, true );
//QgsDebugMsg( QStringLiteral( "localcachefile: %1" ).arg( localcachefile ) );
//QgsDebugMsg( QStringLiteral( "tempcachefile: %1" ).arg( tempcachefile ) );
if ( curpersist )
{
// move cache file from temp dir to local
if ( QFile::exists( localcachefile ) && !QFile::remove( localcachefile ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to delete local token cache file: %1" ).arg( localcachefile ) );
return;
}
if ( QFile::exists( tempcachefile ) && !QFile::copy( tempcachefile, localcachefile ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to copy temp to local token cache file: %1 -> %2" ).arg( tempcachefile, localcachefile ) );
return;
}
if ( QFile::exists( tempcachefile ) && !QFile::remove( tempcachefile ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to delete temp token cache file after copy: %1" ).arg( tempcachefile ) );
return;
}
}
else
{
// move cache file from local to temp
if ( QFile::exists( tempcachefile ) && !QFile::remove( tempcachefile ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to delete temp token cache file: %1" ).arg( tempcachefile ) );
return;
}
if ( QFile::exists( localcachefile ) && !QFile::copy( localcachefile, tempcachefile ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to copy local to temp token cache file: %1 -> %2" ).arg( localcachefile, tempcachefile ) );
return;
}
if ( QFile::exists( localcachefile ) && !QFile::remove( localcachefile ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to delete temp token cache file after copy: %1" ).arg( localcachefile ) );
return;
}
}
}
void QgsAuthOAuth2Edit::tabIndexChanged( int indx )
{
mCurTab = indx;
validateConfig();
}
void QgsAuthOAuth2Edit::populateGrantFlows()
{
cmbbxGrantFlow->addItem( QgsAuthOAuth2Config::grantFlowString( QgsAuthOAuth2Config::AuthCode ),
static_cast<int>( QgsAuthOAuth2Config::AuthCode ) );
cmbbxGrantFlow->addItem( QgsAuthOAuth2Config::grantFlowString( QgsAuthOAuth2Config::Implicit ),
static_cast<int>( QgsAuthOAuth2Config::Implicit ) );
cmbbxGrantFlow->addItem( QgsAuthOAuth2Config::grantFlowString( QgsAuthOAuth2Config::ResourceOwner ),
static_cast<int>( QgsAuthOAuth2Config::ResourceOwner ) );
}
void QgsAuthOAuth2Edit::definedCustomDirChanged( const QString &path )
{
QFileInfo pinfo( path );
bool ok = pinfo.exists() || pinfo.isDir();
leDefinedDirPath->setStyleSheet( ok ? QString() : QgsAuthGuiUtils::redTextStyleSheet() );
updatePredefinedLocationsTooltip();
if ( ok )
{
loadDefinedConfigs();
}
}
void QgsAuthOAuth2Edit::softwareStatementJwtPathChanged( const QString &path )
{
QFileInfo pinfo( path );
bool ok = pinfo.exists() || pinfo.isFile();
leSoftwareStatementJwtPath->setStyleSheet( ok ? QString() : QgsAuthGuiUtils::redTextStyleSheet() );
if ( ok )
{
parseSoftwareStatement( path );
}
}
void QgsAuthOAuth2Edit::setCurrentDefinedConfig( const QString &id )
{
mDefinedId = id;
QgsDebugMsg( QStringLiteral( "Set defined ID: %1" ).arg( id ) );
validateConfig();
}
void QgsAuthOAuth2Edit::currentDefinedItemChanged( QListWidgetItem *cur, QListWidgetItem *prev )
{
Q_UNUSED( prev )
QgsDebugMsg( QStringLiteral( "Entered" ) );
QString id = cur->data( Qt::UserRole ).toString();
if ( !id.isEmpty() )
{
setCurrentDefinedConfig( id );
}
}
void QgsAuthOAuth2Edit::selectCurrentDefinedConfig()
{
if ( mDefinedId.isEmpty() )
{
return;
}
if ( !onDefinedTab() )
{
tabConfigs->setCurrentIndex( definedTab() );
}
lstwdgDefinedConfigs->selectionModel()->clearSelection();
for ( int i = 0; i < lstwdgDefinedConfigs->count(); ++i )
{
QListWidgetItem *itm = lstwdgDefinedConfigs->item( i );
if ( itm->data( Qt::UserRole ).toString() == mDefinedId )
{
lstwdgDefinedConfigs->setCurrentItem( itm, QItemSelectionModel::Select );
break;
}
}
}
void QgsAuthOAuth2Edit::getDefinedCustomDir()
{
QString extradir = QFileDialog::getExistingDirectory( this, tr( "Select extra directory to parse" ),
QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks );
this->raise();
this->activateWindow();
if ( extradir.isEmpty() )
{
return;
}
leDefinedDirPath->setText( extradir );
}
void QgsAuthOAuth2Edit::getSoftStatementDir()
{
QString softStatementFile = QFileDialog::getOpenFileName( this, tr( "Select software statement file" ),
QDir::homePath(), tr( "JSON Web Token (*.jwt)" ) );
this->raise();
this->activateWindow();
if ( softStatementFile.isEmpty() )
{
return;
}
leSoftwareStatementJwtPath->setText( softStatementFile );
}
void QgsAuthOAuth2Edit::initConfigObjs()
{
mOAuthConfigCustom = qgis::make_unique<QgsAuthOAuth2Config>( nullptr );
mOAuthConfigCustom->setConfigType( QgsAuthOAuth2Config::Custom );
mOAuthConfigCustom->setToDefaults();
}
bool QgsAuthOAuth2Edit::hasTokenCacheFile()
{
QString authcfg = parentConfigId();
if ( authcfg.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Auth config ID empty in ID widget of parent" ) );
return false;
}
return ( QFile::exists( QgsAuthOAuth2Config::tokenCachePath( authcfg, false ) )
|| QFile::exists( QgsAuthOAuth2Config::tokenCachePath( authcfg, true ) ) );
}
//slot
void QgsAuthOAuth2Edit::removeTokenCacheFile()
{
QString authcfg = parentConfigId();
if ( authcfg.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Auth config ID empty in ID widget of parent" ) );
return;
}
QStringList cachefiles;
cachefiles << QgsAuthOAuth2Config::tokenCachePath( authcfg, false )
<< QgsAuthOAuth2Config::tokenCachePath( authcfg, true );
Q_FOREACH ( const QString &cachefile, cachefiles )
{
if ( QFile::exists( cachefile ) && !QFile::remove( cachefile ) )
{
QgsDebugMsg( QStringLiteral( "Remove token cache file FAILED for authcfg %1: %2" ).arg( authcfg, cachefile ) );
}
}
btnTokenClear->setEnabled( hasTokenCacheFile() );
}
void QgsAuthOAuth2Edit::updateDefinedConfigsCache()
{
QString extradir = leDefinedDirPath->text();
mDefinedConfigsCache.clear();
mDefinedConfigsCache = QgsAuthOAuth2Config::mappedOAuth2ConfigsCache( this, extradir );
}
void QgsAuthOAuth2Edit::loadDefinedConfigs()
{
whileBlocking( lstwdgDefinedConfigs )->clear();
updateDefinedConfigsCache();
updatePredefinedLocationsTooltip();
QgsStringMap::const_iterator i = mDefinedConfigsCache.constBegin();
while ( i != mDefinedConfigsCache.constEnd() )
{
QgsAuthOAuth2Config *config = new QgsAuthOAuth2Config( this );
if ( !config->loadConfigTxt( i.value().toUtf8(), QgsAuthOAuth2Config::JSON ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to load config for ID: %1" ).arg( i.key() ) );
config->deleteLater();
continue;
}
QString grantflow = QgsAuthOAuth2Config::grantFlowString( config->grantFlow() );
QString name = QStringLiteral( "%1 (%2): %3" )
.arg( config->name(), grantflow, config->description() );
QString tip = tr( "ID: %1\nGrant flow: %2\nDescription: %3" )
.arg( i.key(), grantflow, config->description() );
QListWidgetItem *itm = new QListWidgetItem( lstwdgDefinedConfigs );
itm->setText( name );
itm->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
itm->setData( Qt::UserRole, QVariant( i.key() ) );
itm->setData( Qt::ToolTipRole, QVariant( tip ) );
lstwdgDefinedConfigs->addItem( itm );
config->deleteLater();
++i;
}
if ( lstwdgDefinedConfigs->count() == 0 )
{
QListWidgetItem *itm = new QListWidgetItem( lstwdgDefinedConfigs );
itm->setText( tr( "No predefined configurations found on disk" ) );
QFont f( itm->font() );
f.setItalic( true );
itm->setFont( f );
itm->setFlags( Qt::NoItemFlags );
lstwdgDefinedConfigs->addItem( itm );
}
selectCurrentDefinedConfig();
}
bool QgsAuthOAuth2Edit::onCustomTab() const
{
return mCurTab == customTab();
}
bool QgsAuthOAuth2Edit::onDefinedTab() const
{
return mCurTab == definedTab();
}
bool QgsAuthOAuth2Edit::onStatementTab() const
{
return mCurTab == statementTab();
}
void QgsAuthOAuth2Edit::updateGrantFlow( int indx )
{
if ( cmbbxGrantFlow->currentIndex() != indx )
{
whileBlocking( cmbbxGrantFlow )->setCurrentIndex( indx );
}
QgsAuthOAuth2Config::GrantFlow flow =
static_cast<QgsAuthOAuth2Config::GrantFlow>( cmbbxGrantFlow->itemData( indx ).toInt() );
mOAuthConfigCustom->setGrantFlow( flow );
// bool authcode = ( flow == QgsAuthOAuth2Config::AuthCode );
bool implicit = ( flow == QgsAuthOAuth2Config::Implicit );
bool resowner = ( flow == QgsAuthOAuth2Config::ResourceOwner );
lblRequestUrl->setVisible( !resowner );
leRequestUrl->setVisible( !resowner );
if ( resowner )
leRequestUrl->setText( QString() );
lblRedirectUrl->setVisible( !resowner );
frameRedirectUrl->setVisible( !resowner );
lblClientSecret->setVisible( !implicit );
leClientSecret->setVisible( !implicit );
if ( implicit )
leClientSecret->setText( QString() );
leClientId->setPlaceholderText( resowner ? tr( "Optional" ) : tr( "Required" ) );
leClientSecret->setPlaceholderText( resowner ? tr( "Optional" ) : tr( "Required" ) );
lblUsername->setVisible( resowner );
leUsername->setVisible( resowner );
if ( !resowner )
leUsername->setText( QString() );
lblPassword->setVisible( resowner );
lePassword->setVisible( resowner );
if ( !resowner )
lePassword->setText( QString() );
}
void QgsAuthOAuth2Edit::exportOAuthConfig()
{
if ( !onCustomTab() || !mValid )
{
return;
}
QSettings settings;
QString recentdir = settings.value( QStringLiteral( "UI/lastAuthSaveFileDir" ), QDir::homePath() ).toString();
QString configpath = QFileDialog::getSaveFileName(
this, tr( "Save OAuth2 Config File" ), recentdir, QStringLiteral( "OAuth2 config files (*.json)" ) );
this->raise();
this->activateWindow();
if ( configpath.isEmpty() )
{
return;
}
settings.setValue( QStringLiteral( "UI/lastAuthSaveFileDir" ), QFileInfo( configpath ).absoluteDir().path() );
// give it a kind of random id for re-importing
mOAuthConfigCustom->setId( QgsApplication::authManager()->uniqueConfigId() );
mOAuthConfigCustom->setQueryPairs( queryPairs() );
if ( mParentName && !mParentName->text().isEmpty() )
{
mOAuthConfigCustom->setName( mParentName->text() );
}
if ( !QgsAuthOAuth2Config::writeOAuth2Config( configpath, mOAuthConfigCustom.get(),
QgsAuthOAuth2Config::JSON, true ) )
{
QgsDebugMsg( QStringLiteral( "FAILED to export OAuth2 config file" ) );
}
// clear temp changes
mOAuthConfigCustom->setId( QString::null );
mOAuthConfigCustom->setName( QString::null );
}
void QgsAuthOAuth2Edit::importOAuthConfig()
{
if ( !onCustomTab() )
{
return;
}
QString configfile =
QgsAuthGuiUtils::getOpenFileName( this, tr( "Select OAuth2 Config File" ), QStringLiteral( "OAuth2 config files (*.json)" ) );
this->raise();
this->activateWindow();
QFileInfo importinfo( configfile );
if ( configfile.isEmpty() || !importinfo.exists() )
{
return;
}
QByteArray configtxt;
QFile cfile( configfile );
bool ret = cfile.open( QIODevice::ReadOnly | QIODevice::Text );
if ( ret )
{
configtxt = cfile.readAll();
}
else
{
QgsDebugMsg( QStringLiteral( "FAILED to open config for reading: %1" ).arg( configfile ) );
cfile.close();
return;
}
cfile.close();
if ( configtxt.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "EMPTY read of config: %1" ).arg( configfile ) );
return;
}
QgsStringMap configmap;
configmap.insert( QStringLiteral( "oauth2config" ), QString( configtxt ) );
loadConfig( configmap );
}
void QgsAuthOAuth2Edit::descriptionChanged()
{
mOAuthConfigCustom->setDescription( pteDescription->toPlainText() );
}
void QgsAuthOAuth2Edit::populateAccessMethods()
{
cmbbxAccessMethod->addItem( QgsAuthOAuth2Config::accessMethodString( QgsAuthOAuth2Config::Header ),
static_cast<int>( QgsAuthOAuth2Config::Header ) );
cmbbxAccessMethod->addItem( QgsAuthOAuth2Config::accessMethodString( QgsAuthOAuth2Config::Form ),
static_cast<int>( QgsAuthOAuth2Config::Form ) );
cmbbxAccessMethod->addItem( QgsAuthOAuth2Config::accessMethodString( QgsAuthOAuth2Config::Query ),
static_cast<int>( QgsAuthOAuth2Config::Query ) );
}
void QgsAuthOAuth2Edit::updateConfigAccessMethod( int indx )
{
mOAuthConfigCustom->setAccessMethod( static_cast<QgsAuthOAuth2Config::AccessMethod>( indx ) );
}
void QgsAuthOAuth2Edit::addQueryPairRow( const QString &key, const QString &val )
{
int rowCnt = tblwdgQueryPairs->rowCount();
tblwdgQueryPairs->insertRow( rowCnt );
Qt::ItemFlags itmFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable
| Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
QTableWidgetItem *keyItm = new QTableWidgetItem( key );
keyItm->setFlags( itmFlags );
tblwdgQueryPairs->setItem( rowCnt, 0, keyItm );
QTableWidgetItem *valItm = new QTableWidgetItem( val );
keyItm->setFlags( itmFlags );
tblwdgQueryPairs->setItem( rowCnt, 1, valItm );
}
void QgsAuthOAuth2Edit::populateQueryPairs( const QVariantMap &querypairs, bool append )
{
if ( !append )
{
clearQueryPairs();
}
QVariantMap::const_iterator i = querypairs.constBegin();
while ( i != querypairs.constEnd() )
{
addQueryPairRow( i.key(), i.value().toString() );
++i;
}
}
void QgsAuthOAuth2Edit::queryTableSelectionChanged()
{
bool hassel = tblwdgQueryPairs->selectedItems().count() > 0;
btnRemoveQueryPair->setEnabled( hassel );
}
void QgsAuthOAuth2Edit::updateConfigQueryPairs()
{
mOAuthConfigCustom->setQueryPairs( queryPairs() );
}
QVariantMap QgsAuthOAuth2Edit::queryPairs() const
{
QVariantMap querypairs;
for ( int i = 0; i < tblwdgQueryPairs->rowCount(); ++i )
{
if ( tblwdgQueryPairs->item( i, 0 )->text().isEmpty() )
{
continue;
}
querypairs.insert( tblwdgQueryPairs->item( i, 0 )->text(),
QVariant( tblwdgQueryPairs->item( i, 1 )->text() ) );
}
return querypairs;
}
void QgsAuthOAuth2Edit::addQueryPair()
{
addQueryPairRow( QString(), QString() );
tblwdgQueryPairs->setFocus();
tblwdgQueryPairs->setCurrentCell( tblwdgQueryPairs->rowCount() - 1, 0 );
tblwdgQueryPairs->edit( tblwdgQueryPairs->currentIndex() );
}
void QgsAuthOAuth2Edit::removeQueryPair()
{
tblwdgQueryPairs->removeRow( tblwdgQueryPairs->currentRow() );
}
void QgsAuthOAuth2Edit::clearQueryPairs()
{
for ( int i = tblwdgQueryPairs->rowCount(); i > 0 ; --i )
{
tblwdgQueryPairs->removeRow( i - 1 );
}
}
void QgsAuthOAuth2Edit::parseSoftwareStatement( const QString &path )
{
QFile file( path );
QByteArray softwareStatementBase64;
if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
{
softwareStatementBase64 = file.readAll();
}
if ( softwareStatementBase64.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Error software statement is empty: %1" ).arg( path ) );
file.close();
return;
}
mRegistrationEndpoint = QString();
file.close();
mSoftwareStatement.insert( QStringLiteral( "software_statement" ), softwareStatementBase64 );
QList<QByteArray> payloadParts( softwareStatementBase64.split( '.' ) );
if ( payloadParts.count() < 2 )
{
QgsDebugMsg( QStringLiteral( "Error parsing JSON: base64 decode returned less than 2 parts" ) );
return;
}
QByteArray payload = payloadParts[1];
QByteArray decoded = QByteArray::fromBase64( payload/*, QByteArray::Base64UrlEncoding*/ );
QByteArray errStr;
bool res = false;
const QMap<QString, QVariant> jsonData = QJsonWrapper::parseJson( decoded, &res, &errStr ).toMap();
if ( !res )
{
QgsDebugMsg( QStringLiteral( "Error parsing JSON: %1" ).arg( QString( errStr ) ) );
return;
}
if ( jsonData.contains( QStringLiteral( "grant_types" ) ) && jsonData.contains( QStringLiteral( "redirect_uris" ) ) )
{
const QStringList grantTypes( jsonData[QStringLiteral( "grant_types" ) ].toStringList() );
if ( grantTypes.count( ) )
{
QString grantType = grantTypes[0];
if ( grantType == QLatin1Literal( "authorization_code" ) )
{
updateGrantFlow( static_cast<int>( QgsAuthOAuth2Config::AuthCode ) );
}
else
{
updateGrantFlow( static_cast<int>( QgsAuthOAuth2Config::ResourceOwner ) );
}
}
//Set redirect_uri
const QStringList redirectUris( jsonData[QStringLiteral( "redirect_uris" ) ].toStringList() );
if ( redirectUris.count( ) )
{
QString redirectUri = redirectUris[0];
leRedirectUrl->setText( redirectUri );
}
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Error software statement is invalid: %1" ).arg( path ), 4 );
return;
}
if ( jsonData.contains( QStringLiteral( "registration_endpoint" ) ) )
{
mRegistrationEndpoint = jsonData[QStringLiteral( "registration_endpoint" )].toString();
leSoftwareStatementConfigUrl->setText( mRegistrationEndpoint );
}
QgsDebugMsgLevel( QStringLiteral( "JSON: %1" ).arg( QString::fromLocal8Bit( decoded.data() ) ), 4 );
}
void QgsAuthOAuth2Edit::configReplyFinished()
{
qDebug() << "QgsAuthOAuth2Edit::onConfigReplyFinished";
QNetworkReply *configReply = qobject_cast<QNetworkReply *>( sender() );
if ( configReply->error() == QNetworkReply::NoError )
{
QByteArray replyData = configReply->readAll();
QByteArray errStr;
bool res = false;
QVariantMap config = QJsonWrapper::parseJson( replyData, &res, &errStr ).toMap();
if ( !res )
{
QgsDebugMsg( QStringLiteral( "Error parsing JSON: %1" ).arg( QString( errStr ) ) );
return;
}
// I haven't found any docs about the content of this confg JSON file
// I assume that registration_endpoint is all that it MUST contain.
// But we also MAY have other optional information here
if ( config.contains( QStringLiteral( "registration_endpoint" ) ) )
{
if ( config.contains( QStringLiteral( "authorization_endpoint" ) ) )
leRequestUrl->setText( config.value( QStringLiteral( "authorization_endpoint" ) ).toString() );
if ( config.contains( QStringLiteral( "token_endpoint" ) ) )
leTokenUrl->setText( config.value( QStringLiteral( "token_endpoint" ) ).toString() );
registerSoftStatement( config.value( QStringLiteral( "registration_endpoint" ) ).toString() );
}
else
{
QString errorMsg = tr( "Downloading configuration failed with error: %1" ).arg( configReply->errorString() );
QgsMessageLog::logMessage( errorMsg, QStringLiteral( "OAuth2" ), Qgis::Critical );
}
}
mDownloading = false;
configReply->deleteLater();
}
void QgsAuthOAuth2Edit::registerReplyFinished()
{
//JSV todo
//better error handling
qDebug() << "QgsAuthOAuth2Edit::onRegisterReplyFinished";
QNetworkReply *registerReply = qobject_cast<QNetworkReply *>( sender() );
if ( registerReply->error() == QNetworkReply::NoError )
{
QByteArray replyData = registerReply->readAll();
QByteArray errStr;
bool res = false;
QVariantMap clientInfo = QJsonWrapper::parseJson( replyData, &res, &errStr ).toMap();
// According to RFC 7591 sec. 3.2.1. Client Information Response the only
// required field is client_id
leClientId->setText( clientInfo.value( QStringLiteral( "client_id" ) ).toString() );
if ( clientInfo.contains( QStringLiteral( "client_secret" ) ) )
leClientSecret->setText( clientInfo.value( QStringLiteral( "client_secret" ) ).toString() );
if ( clientInfo.contains( QStringLiteral( "authorization_endpoint" ) ) )
leRequestUrl->setText( clientInfo.value( QStringLiteral( "authorization_endpoint" ) ).toString() );
if ( clientInfo.contains( QStringLiteral( "token_endpoint" ) ) )
leTokenUrl->setText( clientInfo.value( QStringLiteral( "token_endpoint" ) ).toString() );
if ( clientInfo.contains( QStringLiteral( "scopes" ) ) )
leScope->setText( clientInfo.value( QStringLiteral( "scopes" ) ).toString() );
tabConfigs->setCurrentIndex( 0 );
}
else
{
QString errorMsg = QStringLiteral( "Client registration failed with error: %1" ).arg( registerReply->errorString() );
QgsMessageLog::logMessage( errorMsg, QStringLiteral( "OAuth2" ), Qgis::Critical );
}
mDownloading = false;
registerReply->deleteLater();
}
void QgsAuthOAuth2Edit::networkError( QNetworkReply::NetworkError error )
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
qWarning() << "QgsAuthOAuth2Edit::onNetworkError: " << error << ": " << reply->errorString();
QString errorMsg = QStringLiteral( "Network error: %1" ).arg( reply->errorString() );
QgsMessageLog::logMessage( errorMsg, QStringLiteral( "OAuth2" ), Qgis::Critical );
qDebug() << "QgsAuthOAuth2Edit::onNetworkError: " << reply->readAll();
}
void QgsAuthOAuth2Edit::registerSoftStatement( const QString &registrationUrl )
{
QUrl regUrl( registrationUrl );
if ( !regUrl.isValid() )
{
qWarning() << "Registration url is not valid";
return;
}
QByteArray errStr;
bool res = false;
QByteArray json = QJsonWrapper::toJson( QVariant( mSoftwareStatement ), &res, &errStr );
QNetworkRequest registerRequest( regUrl );
QgsSetRequestInitiatorClass( registerRequest, QStringLiteral( "QgsAuthOAuth2Edit" ) );
registerRequest.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1Literal( "application/json" ) );
QNetworkReply *registerReply;
// For testability: use GET if protocol is file://
if ( regUrl.scheme() == QLatin1Literal( "file" ) )
registerReply = QgsNetworkAccessManager::instance()->get( registerRequest );
else
registerReply = QgsNetworkAccessManager::instance()->post( registerRequest, json );
mDownloading = true;
connect( registerReply, &QNetworkReply::finished, this, &QgsAuthOAuth2Edit::registerReplyFinished, Qt::QueuedConnection );
connect( registerReply, qgis::overload<QNetworkReply::NetworkError>::of( &QNetworkReply::error ), this, &QgsAuthOAuth2Edit::networkError, Qt::QueuedConnection );
}
void QgsAuthOAuth2Edit::getSoftwareStatementConfig()
{
if ( !mRegistrationEndpoint.isEmpty() )
{
registerSoftStatement( mRegistrationEndpoint );
}
else
{
QString config = leSoftwareStatementConfigUrl->text();
QUrl configUrl( config );
QNetworkRequest configRequest( configUrl );
QgsSetRequestInitiatorClass( configRequest, QStringLiteral( "QgsAuthOAuth2Edit" ) );
QNetworkReply *configReply = QgsNetworkAccessManager::instance()->get( configRequest );
mDownloading = true;
connect( configReply, &QNetworkReply::finished, this, &QgsAuthOAuth2Edit::configReplyFinished, Qt::QueuedConnection );
connect( configReply, qgis::overload<QNetworkReply::NetworkError>::of( &QNetworkReply::error ), this, &QgsAuthOAuth2Edit::networkError, Qt::QueuedConnection );
}
}
void QgsAuthOAuth2Edit::updatePredefinedLocationsTooltip()
{
const QStringList dirs = QgsAuthOAuth2Config::configLocations( leDefinedDirPath->text() );
QString locationList;
QString locationListHtml;
for ( const QString &dir : dirs )
{
if ( !locationList.isEmpty() )
locationList += '\n';
if ( locationListHtml.isEmpty() )
locationListHtml = QStringLiteral( "<ul>" );
locationList += QStringLiteral( "• %1" ).arg( dir );
locationListHtml += QStringLiteral( "<li><a href=\"%1\">%2</a></li>" ).arg( QUrl::fromLocalFile( dir ).toString(), dir );
}
if ( !locationListHtml.isEmpty() )
locationListHtml += QStringLiteral( "</ul>" );
QString tip = QStringLiteral( "<p>" ) + tr( "Defined configurations are JSON-formatted files, with a single configuration per file. "
"This allows configurations to be swapped out via filesystem tools without affecting user "
"configurations. It is recommended to use the Configure tabs export function, then edit the "
"resulting file. See QGIS documentation for further details." ) + QStringLiteral( "</p><p>" ) +
tr( "Configurations files can be placed in the directories:" ) + QStringLiteral( "</p>" ) + locationListHtml;
pteDefinedDesc->setHtml( tip );
lstwdgDefinedConfigs->setToolTip( tr( "Configuration files can be placed in the directories:\n\n%1" ).arg( locationList ) );
}