[oauth] Client registration with JWT

Ported from https://github.com/securedimensions/QGIS-OAuth2-Plugin

The Testbed 13 version provides an additional configuration tab "software statement" which allows a user to automatically register the plugin with a required configuration with the Authorization Server. Of course this can only be leveraged, if the Authorization Server involved supports the registration via digitally signed software statements (JWTs) as described in this ER.
This commit is contained in:
Alessandro Pasotti 2018-07-19 17:51:07 +02:00
parent c50e99e699
commit d56fc885f6
12 changed files with 510 additions and 7 deletions

View File

@ -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;
}

View File

@ -16,6 +16,7 @@
#define QGSAUTHOAUTH2EDIT_H
#include <QWidget>
#include <QNetworkReply>
#include "qgsauthmethodedit.h"
#include "ui_qgsauthoauth2edit.h"
@ -50,6 +51,7 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
*/
QgsStringMap configMap() const override;
public slots:
//! Load the configuration from \a configMap
@ -69,30 +71,42 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
void removeTokenCacheFile();
void populateGrantFlows();
void updateGrantFlow( int indx );
void exportOAuthConfig();
void importOAuthConfig();
void descriptionChanged();
void populateAccessMethods();
void updateConfigAccessMethod( int indx );
void addQueryPair();
void removeQueryPair();
void clearQueryPairs();
void populateQueryPairs( const QVariantMap &querypairs, bool append = false );
void queryTableSelectionChanged();
void updateConfigQueryPairs();
void updateDefinedConfigsCache();
void loadDefinedConfigs();
void setCurrentDefinedConfig( const QString &id );
void currentDefinedItemChanged( QListWidgetItem *cur, QListWidgetItem *prev );
void selectCurrentDefinedConfig();
void loadFromOAuthConfig( const QgsAuthOAuth2Config *config = nullptr );
void getSoftStatementDir();
void updateTokenCacheFile( bool curpersist ) const;
@ -102,8 +116,26 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
void getDefinedCustomDir();
void loadFromOAuthConfig( const QgsAuthOAuth2Config *config );
void softwareStatementJwtPathChanged( const QString &path );
void configReplyFinished();
void registerReplyFinished();
void networkError(QNetworkReply::NetworkError error);
//! For testability
QString registrationEndpoint() const;
//! For testability
void setRegistrationEndpoint(const QString& registrationEndpoint);
private:
void initGui();
void parseSoftwareStatement(const QString& path);
QWidget *parentWidget() const;
QLineEdit *parentNameField() const;
@ -118,8 +150,11 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
int customTab() const { return 0; }
int definedTab() const { return 1; }
int statementTab() const { return 2; }
bool onCustomTab() const;
bool onDefinedTab() const;
bool onStatementTab() const;
void getSoftwareStatementConfig();
QString currentDefinedConfig() const { return mDefinedId; }
@ -132,6 +167,11 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
int mCurTab = 0;
bool mPrevPersistToken = false;
QToolButton *btnTokenClear = nullptr;
QString mRegistrationEndpoint;
QMap<QString, QVariant> mSoftwareStatement;
void registerSoftStatement(const QString& registrationUrl);
bool mDownloading = false;
friend class TestQgsAuthOAuth2Method;
};
#endif // QGSAUTHOAUTH2EDIT_H

View File

@ -75,7 +75,7 @@
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="tabCustom">
<attribute name="title">
@ -202,7 +202,7 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-241</y>
<y>0</y>
<width>401</width>
<height>516</height>
</rect>
@ -734,6 +734,85 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tabStatement">
<attribute name="title">
<string>Software Statement</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>6</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QLabel" name="lblSoftStatementDir">
<property name="text">
<string>Software Statement</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="leSoftwareStatementConfigUrl">
<property name="placeholderText">
<string>Optional</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="btnSoftStatementDir">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="oauth2_resources.qrc">
<normaloff>:/oauth2method/oauth2_resources/fileopen.svg</normaloff>:/oauth2method/oauth2_resources/fileopen.svg</iconset>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="lblConfigUrl">
<property name="text">
<string>Configuration Url</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="leSoftwareStatementJwtPath">
<property name="placeholderText">
<string>Required</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="btnRegister">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Register</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
<widget class="QgsCollapsibleGroupBoxBasic" name="grpbxAdvanced">
<property name="sizePolicy">

View File

@ -29,6 +29,7 @@
#include "qgssettings.h"
#include <QDateTime>
#include <QInputDialog>
#include <QDesktopServices>
#include <QDir>
#include <QEventLoop>
@ -173,7 +174,8 @@ bool QgsAuthOAuth2Method::updateNetworkRequest( QNetworkRequest &request, const
connect( o2, &QgsO2::linkingSucceeded, this, &QgsAuthOAuth2Method::onLinkingSucceeded, Qt::UniqueConnection );
connect( o2, &QgsO2::openBrowser, this, &QgsAuthOAuth2Method::onOpenBrowser, Qt::UniqueConnection );
connect( o2, &QgsO2::closeBrowser, this, &QgsAuthOAuth2Method::onCloseBrowser, Qt::UniqueConnection );
connect( o2, &QgsO2::getAuthCode, this, &QgsAuthOAuth2Method::onAuthCode, Qt::UniqueConnection );
connect( this, &QgsAuthOAuth2Method::setAuthCode, o2, &QgsO2::onSetAuthCode, Qt::UniqueConnection );
//qRegisterMetaType<QNetworkReply::NetworkError>( QStringLiteral( "QNetworkReply::NetworkError" )) // for Qt::QueuedConnection, if needed;
connect( o2, &QgsO2::refreshFinished, this, &QgsAuthOAuth2Method::onRefreshFinished, Qt::UniqueConnection );
@ -458,6 +460,16 @@ void QgsAuthOAuth2Method::onRefreshFinished( QNetworkReply::NetworkError err )
}
}
void QgsAuthOAuth2Method::onAuthCode()
{
bool ok = false;
QString code = QInputDialog::getText( QApplication::activeWindow(), QStringLiteral( "Enter the authorization code" ) , QStringLiteral("Authoriation code" ), QLineEdit::Normal, QStringLiteral( "Required" ), &ok, Qt::Dialog, Qt::InputMethodHint::ImhNone);
if( ok && !code.isEmpty())
{
emit setAuthCode(code);
}
}
bool QgsAuthOAuth2Method::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
const QString &dataprovider )
{

View File

@ -71,22 +71,36 @@ class QgsAuthOAuth2Method : public QgsAuthMethod
//! Triggered when linked condition has changed
void onLinkedChanged();
//! Triggered when linking operation failed
void onLinkingFailed();
//! Triggered when linking operation succeeded
void onLinkingSucceeded();
//! Triggered when the browser needs to be opened at \a url
void onOpenBrowser( const QUrl &url );
//! Triggered on browser close
void onCloseBrowser();
//! Triggered on reply finished
void onReplyFinished();
//! Triggered on network error
void onNetworkError( QNetworkReply::NetworkError err );
//! Triggered on refresh finished
void onRefreshFinished( QNetworkReply::NetworkError err );
//! Triggered when auth code needs to be manually entered by the user
void onAuthCode();
signals:
//! Emitted when authcode was manually set by the user
void setAuthCode( const QString code );
private:
QString mTempStorePath;

View File

@ -144,6 +144,12 @@ void QgsO2::clearProperties()
// TODO: clear object properties
}
void QgsO2::onSetAuthCode(const QString& code)
{
setCode( code );
onVerificationReceived( QMap<QString, QString>() );
}
void QgsO2::link()
{
QgsDebugMsgLevel( QStringLiteral( "QgsO2::link" ), 4 );

View File

@ -63,6 +63,9 @@ class QgsO2: public O2
//! Clear all properties
void clearProperties();
//! Triggered when auth code was set
void onSetAuthCode(const QString &code);
//! Authenticate.
void link() override;

View File

@ -28,6 +28,7 @@
#include "qgsapplication.h"
#include "qgsauthmanager.h"
#include "qgsauthoauth2config.h"
#include "qgsauthoauth2edit.h"
/**
@ -47,6 +48,9 @@ class TestQgsAuthOAuth2Method: public QObject
void testOAuth2Config();
void testOAuth2ConfigIO();
void testOAuth2ConfigUtils();
void testDynamicRegistration();
void testDynamicRegistrationJwt();
void testDynamicRegistrationNoEndpoint();
private:
QgsAuthOAuth2Config *baseConfig( bool loaded = false );
@ -56,8 +60,13 @@ class TestQgsAuthOAuth2Method: public QObject
QByteArray baseVariantTxt();
static QString smHashes;
static QString sTestDataDir;
};
QString TestQgsAuthOAuth2Method::sTestDataDir = QStringLiteral( TEST_DATA_DIR ) + "/auth_system/oauth2";
QString TestQgsAuthOAuth2Method::smHashes = "#####################";
//QObject *TestQgsAuthOAuth2Method::smParentObj = new QObject();
@ -400,5 +409,100 @@ void TestQgsAuthOAuth2Method::testOAuth2ConfigUtils()
}
void TestQgsAuthOAuth2Method::testDynamicRegistrationNoEndpoint()
{
QgsAuthOAuth2Config *config = baseConfig();
config->setClientId( QString( ));
config->setClientSecret( QString( ));
QVariantMap configMap( config->mappedProperties() );
QCOMPARE(configMap["clientId"], QString());
QCOMPARE(configMap["clientSecret"], QString());
QgsAuthOAuth2Edit dlg;
QgsStringMap stringMap;
for ( const auto &k: configMap.keys( ))
{
stringMap[k] = configMap.value(k).toString();
}
dlg.loadConfig(stringMap);
QCOMPARE(dlg.leClientId->text(), QString());
QCOMPARE(dlg.leClientSecret->text(), QString());
// This JWT does not contain a registration_endpoint
dlg.leSoftwareStatementJwtPath->setText( QStringLiteral( "%1/auth_code_grant_display_code.jwt" ).arg( sTestDataDir ) );
QVERIFY( ! dlg.btnRegister->isEnabled() );
QCOMPARE( dlg.leSoftwareStatementConfigUrl->text(), QString() );
}
void TestQgsAuthOAuth2Method::testDynamicRegistration()
{
QgsAuthOAuth2Config *config = baseConfig();
config->setClientId( QString( ));
config->setClientSecret( QString( ));
QVariantMap configMap( config->mappedProperties() );
QCOMPARE(configMap["clientId"], QString());
QCOMPARE(configMap["clientSecret"], QString());
QgsAuthOAuth2Edit dlg;
QgsStringMap stringMap;
for ( const auto &k: configMap.keys( ))
{
stringMap[k] = configMap.value(k).toString();
}
dlg.loadConfig(stringMap);
QCOMPARE(dlg.leClientId->text(), QString());
QCOMPARE(dlg.leClientSecret->text(), QString());
// This JWT does not contain a registration_endpoint
dlg.leSoftwareStatementJwtPath->setText( QStringLiteral( "%1/auth_code_grant_display_code.jwt" ).arg( sTestDataDir ) );
QVERIFY( ! dlg.btnRegister->isEnabled() );
QCOMPARE( dlg.leSoftwareStatementConfigUrl->text(), QString() );
// Set the config url to something local
dlg.leSoftwareStatementConfigUrl->setText( QUrl::fromLocalFile( QStringLiteral( "%1/auth_code_grant_display_code_get_config.json" ).arg( sTestDataDir )).toString( ));
QVERIFY( dlg.btnRegister->isEnabled() );
// Change it to something local
dlg.setRegistrationEndpoint( QUrl::fromLocalFile( QStringLiteral( "%1/client_information_registration_response.json" ).arg( sTestDataDir )).toString());
QTest::mouseClick( dlg.btnRegister, Qt::MouseButton::LeftButton );
while ( dlg.mDownloading )
{
qApp->processEvents();
}
QCOMPARE(dlg.leClientId->text(), QLatin1Literal( "___QGIS_ROCKS___@www.qgis.org"));
QCOMPARE(dlg.leClientSecret->text(), QLatin1Literal( "___QGIS_ROCKS______QGIS_ROCKS______QGIS_ROCKS___" ));
}
void TestQgsAuthOAuth2Method::testDynamicRegistrationJwt()
{
QgsAuthOAuth2Config *config = baseConfig();
config->setClientId( QString( ));
config->setClientSecret( QString( ));
QVariantMap configMap( config->mappedProperties() );
QCOMPARE(configMap["clientId"], QString());
QCOMPARE(configMap["clientSecret"], QString());
QgsAuthOAuth2Edit dlg;
QgsStringMap stringMap;
for ( const auto &k: configMap.keys( ))
{
stringMap[k] = configMap.value(k).toString();
}
dlg.loadConfig(stringMap);
QCOMPARE(dlg.leClientId->text(), QString());
QCOMPARE(dlg.leClientSecret->text(), QString());
// Now set the config URL to the JWT that does contain a registration_endpoint
dlg.leSoftwareStatementJwtPath->setText( QStringLiteral( "%1/auth_code_grant_display_code_registration_endpoint.jwt" ).arg( sTestDataDir ));
QCOMPARE( dlg.leSoftwareStatementConfigUrl->text(), QStringLiteral("http://www.qgis.org/oauth2/registration") );
QVERIFY( dlg.btnRegister->isEnabled() );
// Change it to something local
dlg.setRegistrationEndpoint( QUrl::fromLocalFile( QStringLiteral( "%1/client_information_registration_response.json" ).arg( sTestDataDir )).toString() );
QTest::mouseClick( dlg.btnRegister, Qt::MouseButton::LeftButton );
while ( dlg.mDownloading )
{
qApp->processEvents();
}
QCOMPARE(dlg.leClientId->text(), QLatin1Literal( "___QGIS_ROCKS___@www.qgis.org"));
QCOMPARE(dlg.leClientSecret->text(), QLatin1Literal( "___QGIS_ROCKS______QGIS_ROCKS______QGIS_ROCKS___" ));
}
QGSTEST_MAIN( TestQgsAuthOAuth2Method )
#include "testqgsauthoauth2method.moc"

View File

@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJ3d3cuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJhdWQiOiJhcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlIiwic29mdHdhcmVfaWQiOiIwZTNlZmYyMi01NjkwLTQzMDYtYWFmYi04ZDRkMGY1Yjk2N2IiLCJzb2Z0d2FyZV92ZXJzaW9uIjoiMi4xOC40eCIsImNsaWVudF9uYW1lIjoiUUdJUyBPQXV0aDIgUGx1Z2luIiwiY2xpZW50X3VyaSI6Imh0dHBzOi8vcWdpcy5vcmciLCJyZWRpcmVjdF91cmlzIjpbImh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS9vYXV0aC9kaXNwbGF5X2NvZGUucGhwIl0sInRva2VuX2VuZHBvaW50X2F1dGhfbWV0aG9kIjoiY2xpZW50X3NlY3JldF9wb3N0IiwiZ3JhbnRfdHlwZXMiOlsiYXV0aG9yaXphdGlvbl9jb2RlIl0sInJlc3BvbnNlX3R5cGVzIjpbImNvZGUiXSwibG9nb191cmkiOiJodHRwczovL2h1Yi5xZ2lzLm9yZy9hdHRhY2htZW50cy80MDAzL1FHaXNfTG9nby5wbmciLCJzY29wZSI6Im9wZW5pZCBiZWVsZCIsImNvbnRhY3RzIjpbIm1haWx0bzphbUBzZWN1cmUtZGltZW5zaW9ucy5kZSJdLCJ0b3NfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtdG9zLmh0bWwiLCJwb2xpY3lfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtcG9saWN5Lmh0bWwiLCJqd2tzX3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS8ud2VsbC1rbm93bi9qd2tzLmpzb24iLCJraWQiOiJTRFB1YmxpY0tleSIsImlhdCI6MTQ5OTE1MDY3M30.M4ablN_BfmHjVPYZo_uVrCDYLsaalhF_aRsS-hppT9AJd8DAGZn_xYrsq8FIaKjZhivjKHtjbR8zyFee0HjU6iMP9KgC0N_jPn3o5L2n8IEl7oRJ5zW9V-v2SqBdaXffm7TBx7v8KQ2J4uaoARYWgAkBcVVxKZWX9kLgQfaaoSu2Zk-aoMNqzNU8u2UTADpoCwGjOd10ik0ZdQ6VC2czfAfYEmfYg4UYPQDafKjWWEXUhO9PXzD0piv_NJ9o2oDdc34KBj8brZRDbDfuvIP5dxpaDh8PWhVE4Dd7dEsAzPXEFdXnLTGzQ_5zDj_CcUQqJ0SzkohALGhpPgy51Bjwsw

View File

@ -0,0 +1,3 @@
{
"registration_endpoint": "http://www.qgis.org/oauth2/register"
}

View File

@ -0,0 +1 @@
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfbmFtZSI6IlFHSVMgT0F1dGgyIFBsdWdpbiIsInNvZnR3YXJlX3ZlcnNpb24iOiIyLjE4LjR4IiwicG9saWN5X3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS9xZ2lzLXBvbGljeS5odG1sIiwic29mdHdhcmVfaWQiOiIwZTNlZmYyMi01NjkwLTQzMDYtYWFmYi04ZDRkMGY1Yjk2N2IiLCJjbGllbnRfdXJpIjoiaHR0cHM6Ly9xZ2lzLm9yZyIsImdyYW50X3R5cGVzIjpbImF1dGhvcml6YXRpb25fY29kZSJdLCJpc3MiOiJ3d3cuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJyZXNwb25zZV90eXBlcyI6WyJjb2RlIl0sImF1ZCI6ImFzLnRiMTMuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJ0b3NfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtdG9zLmh0bWwiLCJ0b2tlbl9lbmRwb2ludF9hdXRoX21ldGhvZCI6ImNsaWVudF9zZWNyZXRfcG9zdCIsImNvbnRhY3RzIjpbIm1haWx0bzphbUBzZWN1cmUtZGltZW5zaW9ucy5kZSJdLCJqd2tzX3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS8ud2VsbC1rbm93bi9qd2tzLmpzb24iLCJzY29wZSI6Im9wZW5pZCBiZWVsZCIsInJlZGlyZWN0X3VyaXMiOlsiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL29hdXRoL2Rpc3BsYXlfY29kZS5waHAiXSwicmVnaXN0cmF0aW9uX2VuZHBvaW50IjoiaHR0cDovL3d3dy5xZ2lzLm9yZy9vYXV0aDIvcmVnaXN0cmF0aW9uIiwibG9nb191cmkiOiJodHRwczovL2h1Yi5xZ2lzLm9yZy9hdHRhY2htZW50cy80MDAzL1FHaXNfTG9nby5wbmciLCJraWQiOiJTRFB1YmxpY0tleSJ9.kd5lIGXjCYPIgffKton3NaTSdIB8KueYpu48vXbydGY

View File

@ -0,0 +1,4 @@
{
"client_id": "___QGIS_ROCKS___@www.qgis.org",
"client_secret": "___QGIS_ROCKS______QGIS_ROCKS______QGIS_ROCKS___"
}