@@ -22,8 +22,9 @@
#include " qgsauthguiutils.h"
#include " qgsauthmanager.h"
#include " qgsauthconfigedit.h"
#include " qgslogger.h"
#include " qgsmessagelog.h"
#include " qgsnetworkaccessmanager.h"
#include " qjsonwrapper/Json.h"
QgsAuthOAuth2Edit::QgsAuthOAuth2Edit ( QWidget *parent )
: QgsAuthMethodEdit( parent )
@@ -146,6 +147,17 @@ void QgsAuthOAuth2Edit::setupConnections()
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, [ = ] ( const QString &txt ) {
btnRegister->setEnabled ( QUrl ( txt ).isValid () && ! leSoftwareStatementJwtPath->text ().isEmpty () );
});
connect ( btnRegister, &QPushButton::clicked, this , &QgsAuthOAuth2Edit::getSoftwareStatementConfig );
// FIXME: in the testbed13 code this signal does not exists (but a connection was attempted)
// connect( this, &QgsAuthOAuth2Edit::configSucceeded, this, &QgsAuthOAuth2Edit::registerSoftStatement );
// Custom config editing connections
connect ( cmbbxGrantFlow, static_cast <void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
this , &QgsAuthOAuth2Edit::updateGrantFlow ); // also updates GUI
@@ -353,7 +365,6 @@ void QgsAuthOAuth2Edit::clearConfig()
loadFromOAuthConfig ( mOAuthConfigCustom .get () );
}
// slot
void QgsAuthOAuth2Edit::loadFromOAuthConfig ( const QgsAuthOAuth2Config *config )
{
if ( !config )
@@ -494,6 +505,21 @@ void QgsAuthOAuth2Edit::definedCustomDirChanged( const QString &path )
}
}
void QgsAuthOAuth2Edit::softwareStatementJwtPathChanged ( const QString &path )
{
QFileInfo pinfo ( path );
bool ok = pinfo.exists () || pinfo.isFile ();
leSoftwareStatementJwtPath->setStyleSheet ( ok ? " " : QgsAuthGuiUtils::redTextStyleSheet () );
if ( ok )
{
parseSoftwareStatement ( path );
}
}
// slot
void QgsAuthOAuth2Edit::setCurrentDefinedConfig ( const QString &id )
{
@@ -557,6 +583,20 @@ void QgsAuthOAuth2Edit::getDefinedCustomDir()
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.isNull () )
{
return ;
}
leSoftwareStatementJwtPath->setText ( softStatementFile );
}
void QgsAuthOAuth2Edit::initConfigObjs ()
{
mOAuthConfigCustom = qgis::make_unique<QgsAuthOAuth2Config>( nullptr );
@@ -673,6 +713,11 @@ bool QgsAuthOAuth2Edit::onDefinedTab() const
return mCurTab == definedTab ();
}
bool QgsAuthOAuth2Edit::onStatementTab () const
{
return mCurTab == statementTab ();
}
// slot
void QgsAuthOAuth2Edit::updateGrantFlow ( int indx )
{
@@ -910,3 +955,194 @@ void QgsAuthOAuth2Edit::clearQueryPairs()
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 ( QString ( path ) ) );
file.close ();
return ;
}
file.close ();
mSoftwareStatement .insert (" software_statement" ,softwareStatementBase64);
QByteArray payload=softwareStatementBase64.split (' .' )[1 ];
QByteArray decoded=QByteArray::fromBase64 (payload/* , QByteArray::Base64UrlEncoding*/ );
QByteArray errStr;
bool res = false ;
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 (" grant_types" ) && jsonData.contains ( QLatin1Literal ( " redirect_uris" ) ) )
{
QString grantType = jsonData[QLatin1Literal ( " grant_types" ) ].toStringList ()[0 ];
if (grantType == QLatin1Literal ( " authorization_code" ) )
{
updateGrantFlow ( static_cast <int >( QgsAuthOAuth2Config::AuthCode ) );
}
else
{
updateGrantFlow ( static_cast <int >( QgsAuthOAuth2Config::ResourceOwner ) );
}
// Set redirect_uri
QString redirectUri = jsonData[QLatin1Literal ( " redirect_uris" ) ].toStringList ()[0 ];
leRedirectUrl->setText (redirectUri);
}
else
{
QgsDebugMsgLevel ( QStringLiteral ( " Error software statement is invalid: %1" ).arg ( QString ( path ) ), 4 );
return ;
}
if (jsonData.contains (QLatin1Literal ( " registration_endpoint" )) )
{
mRegistrationEndpoint = jsonData[QLatin1Literal (" 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 contains
// But we also might have other optional information here
if (config.contains (QLatin1Literal ( " registration_endpoint" )) )
{
if ( config.contains (QLatin1Literal (" authorization_endpoint" ) ) )
leRequestUrl->setText (config.value (QLatin1Literal (" authorization_endpoint" ) ).toString ());
if ( config.contains (QLatin1Literal (" token_endpoint" ) ) )
leTokenUrl->setText (config.value (QLatin1Literal (" token_endpoint" ) ).toString ());
registerSoftStatement (config.value (QLatin1Literal (" registration_endpoint" )).toString ());
}
else
{
QString errorMsg = QStringLiteral ( " 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 (QLatin1Literal (" client_id" ) ).toString ());
if ( clientInfo.contains (QLatin1Literal (" client_secret" )) )
leClientSecret->setText (clientInfo.value (QLatin1Literal (" client_secret" ) ).toString ());
if ( clientInfo.contains (QLatin1Literal (" authorization_endpoint" ) ) )
leRequestUrl->setText (clientInfo.value (QLatin1Literal (" authorization_endpoint" ) ).toString ());
if ( clientInfo.contains (QLatin1Literal (" token_endpoint" ) ) )
leTokenUrl->setText (clientInfo.value (QLatin1Literal (" token_endpoint" ) ).toString ());
if ( clientInfo.contains (QLatin1Literal (" scopes" ) ) )
leScope->setText (clientInfo.value (QLatin1Literal (" scopes" ) ).toString ());
tabConfigs->setCurrentIndex (0 );
}
else
{
QString errorMsg = QStringLiteral ( " Client registration failed with error: %1" ).arg ( registerReply->errorString () );
QgsMessageLog::logMessage ( errorMsg, QLatin1Literal ( " 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, QLatin1Literal ( " 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);
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);
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);
}
}
QString QgsAuthOAuth2Edit::registrationEndpoint () const
{
return mRegistrationEndpoint ;
}
void QgsAuthOAuth2Edit::setRegistrationEndpoint (const QString& registrationEndpoint)
{
mRegistrationEndpoint = registrationEndpoint;
}