mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-08 00:05:09 -04:00
[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:
parent
c50e99e699
commit
d56fc885f6
@ -22,8 +22,9 @@
|
|||||||
#include "qgsauthguiutils.h"
|
#include "qgsauthguiutils.h"
|
||||||
#include "qgsauthmanager.h"
|
#include "qgsauthmanager.h"
|
||||||
#include "qgsauthconfigedit.h"
|
#include "qgsauthconfigedit.h"
|
||||||
#include "qgslogger.h"
|
#include "qgsmessagelog.h"
|
||||||
|
#include "qgsnetworkaccessmanager.h"
|
||||||
|
#include "qjsonwrapper/Json.h"
|
||||||
|
|
||||||
QgsAuthOAuth2Edit::QgsAuthOAuth2Edit( QWidget *parent )
|
QgsAuthOAuth2Edit::QgsAuthOAuth2Edit( QWidget *parent )
|
||||||
: QgsAuthMethodEdit( parent )
|
: QgsAuthMethodEdit( parent )
|
||||||
@ -146,6 +147,17 @@ void QgsAuthOAuth2Edit::setupConnections()
|
|||||||
connect( btnGetDefinedDirPath, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::getDefinedCustomDir );
|
connect( btnGetDefinedDirPath, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::getDefinedCustomDir );
|
||||||
connect( leDefinedDirPath, &QLineEdit::textChanged, this, &QgsAuthOAuth2Edit::definedCustomDirChanged );
|
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
|
// Custom config editing connections
|
||||||
connect( cmbbxGrantFlow, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
|
connect( cmbbxGrantFlow, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
|
||||||
this, &QgsAuthOAuth2Edit::updateGrantFlow ); // also updates GUI
|
this, &QgsAuthOAuth2Edit::updateGrantFlow ); // also updates GUI
|
||||||
@ -353,7 +365,6 @@ void QgsAuthOAuth2Edit::clearConfig()
|
|||||||
loadFromOAuthConfig( mOAuthConfigCustom.get() );
|
loadFromOAuthConfig( mOAuthConfigCustom.get() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// slot
|
|
||||||
void QgsAuthOAuth2Edit::loadFromOAuthConfig( const QgsAuthOAuth2Config *config )
|
void QgsAuthOAuth2Edit::loadFromOAuthConfig( const QgsAuthOAuth2Config *config )
|
||||||
{
|
{
|
||||||
if ( !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
|
// slot
|
||||||
void QgsAuthOAuth2Edit::setCurrentDefinedConfig( const QString &id )
|
void QgsAuthOAuth2Edit::setCurrentDefinedConfig( const QString &id )
|
||||||
{
|
{
|
||||||
@ -557,6 +583,20 @@ void QgsAuthOAuth2Edit::getDefinedCustomDir()
|
|||||||
leDefinedDirPath->setText( extradir );
|
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()
|
void QgsAuthOAuth2Edit::initConfigObjs()
|
||||||
{
|
{
|
||||||
mOAuthConfigCustom = qgis::make_unique<QgsAuthOAuth2Config>( nullptr );
|
mOAuthConfigCustom = qgis::make_unique<QgsAuthOAuth2Config>( nullptr );
|
||||||
@ -673,6 +713,11 @@ bool QgsAuthOAuth2Edit::onDefinedTab() const
|
|||||||
return mCurTab == definedTab();
|
return mCurTab == definedTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QgsAuthOAuth2Edit::onStatementTab() const
|
||||||
|
{
|
||||||
|
return mCurTab == statementTab();
|
||||||
|
}
|
||||||
|
|
||||||
// slot
|
// slot
|
||||||
void QgsAuthOAuth2Edit::updateGrantFlow( int indx )
|
void QgsAuthOAuth2Edit::updateGrantFlow( int indx )
|
||||||
{
|
{
|
||||||
@ -910,3 +955,194 @@ void QgsAuthOAuth2Edit::clearQueryPairs()
|
|||||||
tblwdgQueryPairs->removeRow( i - 1 );
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#define QGSAUTHOAUTH2EDIT_H
|
#define QGSAUTHOAUTH2EDIT_H
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <QNetworkReply>
|
||||||
#include "qgsauthmethodedit.h"
|
#include "qgsauthmethodedit.h"
|
||||||
#include "ui_qgsauthoauth2edit.h"
|
#include "ui_qgsauthoauth2edit.h"
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
|
|||||||
*/
|
*/
|
||||||
QgsStringMap configMap() const override;
|
QgsStringMap configMap() const override;
|
||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
//! Load the configuration from \a configMap
|
//! Load the configuration from \a configMap
|
||||||
@ -69,30 +71,42 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
|
|||||||
void removeTokenCacheFile();
|
void removeTokenCacheFile();
|
||||||
|
|
||||||
void populateGrantFlows();
|
void populateGrantFlows();
|
||||||
|
|
||||||
void updateGrantFlow( int indx );
|
void updateGrantFlow( int indx );
|
||||||
|
|
||||||
void exportOAuthConfig();
|
void exportOAuthConfig();
|
||||||
|
|
||||||
void importOAuthConfig();
|
void importOAuthConfig();
|
||||||
|
|
||||||
void descriptionChanged();
|
void descriptionChanged();
|
||||||
|
|
||||||
void populateAccessMethods();
|
void populateAccessMethods();
|
||||||
|
|
||||||
void updateConfigAccessMethod( int indx );
|
void updateConfigAccessMethod( int indx );
|
||||||
|
|
||||||
void addQueryPair();
|
void addQueryPair();
|
||||||
|
|
||||||
void removeQueryPair();
|
void removeQueryPair();
|
||||||
|
|
||||||
void clearQueryPairs();
|
void clearQueryPairs();
|
||||||
|
|
||||||
void populateQueryPairs( const QVariantMap &querypairs, bool append = false );
|
void populateQueryPairs( const QVariantMap &querypairs, bool append = false );
|
||||||
|
|
||||||
void queryTableSelectionChanged();
|
void queryTableSelectionChanged();
|
||||||
|
|
||||||
void updateConfigQueryPairs();
|
void updateConfigQueryPairs();
|
||||||
|
|
||||||
void updateDefinedConfigsCache();
|
void updateDefinedConfigsCache();
|
||||||
|
|
||||||
void loadDefinedConfigs();
|
void loadDefinedConfigs();
|
||||||
|
|
||||||
void setCurrentDefinedConfig( const QString &id );
|
void setCurrentDefinedConfig( const QString &id );
|
||||||
|
|
||||||
void currentDefinedItemChanged( QListWidgetItem *cur, QListWidgetItem *prev );
|
void currentDefinedItemChanged( QListWidgetItem *cur, QListWidgetItem *prev );
|
||||||
|
|
||||||
void selectCurrentDefinedConfig();
|
void selectCurrentDefinedConfig();
|
||||||
|
|
||||||
void loadFromOAuthConfig( const QgsAuthOAuth2Config *config = nullptr );
|
void getSoftStatementDir();
|
||||||
|
|
||||||
void updateTokenCacheFile( bool curpersist ) const;
|
void updateTokenCacheFile( bool curpersist ) const;
|
||||||
|
|
||||||
@ -102,8 +116,26 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
|
|||||||
|
|
||||||
void getDefinedCustomDir();
|
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:
|
private:
|
||||||
|
|
||||||
void initGui();
|
void initGui();
|
||||||
|
void parseSoftwareStatement(const QString& path);
|
||||||
|
|
||||||
QWidget *parentWidget() const;
|
QWidget *parentWidget() const;
|
||||||
QLineEdit *parentNameField() const;
|
QLineEdit *parentNameField() const;
|
||||||
@ -118,8 +150,11 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
|
|||||||
|
|
||||||
int customTab() const { return 0; }
|
int customTab() const { return 0; }
|
||||||
int definedTab() const { return 1; }
|
int definedTab() const { return 1; }
|
||||||
|
int statementTab() const { return 2; }
|
||||||
bool onCustomTab() const;
|
bool onCustomTab() const;
|
||||||
bool onDefinedTab() const;
|
bool onDefinedTab() const;
|
||||||
|
bool onStatementTab() const;
|
||||||
|
void getSoftwareStatementConfig();
|
||||||
|
|
||||||
QString currentDefinedConfig() const { return mDefinedId; }
|
QString currentDefinedConfig() const { return mDefinedId; }
|
||||||
|
|
||||||
@ -132,6 +167,11 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
|
|||||||
int mCurTab = 0;
|
int mCurTab = 0;
|
||||||
bool mPrevPersistToken = false;
|
bool mPrevPersistToken = false;
|
||||||
QToolButton *btnTokenClear = nullptr;
|
QToolButton *btnTokenClear = nullptr;
|
||||||
|
QString mRegistrationEndpoint;
|
||||||
|
QMap<QString, QVariant> mSoftwareStatement;
|
||||||
|
void registerSoftStatement(const QString& registrationUrl);
|
||||||
|
bool mDownloading = false;
|
||||||
|
friend class TestQgsAuthOAuth2Method;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // QGSAUTHOAUTH2EDIT_H
|
#endif // QGSAUTHOAUTH2EDIT_H
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>2</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="tabCustom">
|
<widget class="QWidget" name="tabCustom">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
@ -202,7 +202,7 @@
|
|||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>-241</y>
|
<y>0</y>
|
||||||
<width>401</width>
|
<width>401</width>
|
||||||
<height>516</height>
|
<height>516</height>
|
||||||
</rect>
|
</rect>
|
||||||
@ -734,6 +734,85 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</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>
|
||||||
<widget class="QgsCollapsibleGroupBoxBasic" name="grpbxAdvanced">
|
<widget class="QgsCollapsibleGroupBoxBasic" name="grpbxAdvanced">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "qgssettings.h"
|
#include "qgssettings.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
@ -173,7 +174,8 @@ bool QgsAuthOAuth2Method::updateNetworkRequest( QNetworkRequest &request, const
|
|||||||
connect( o2, &QgsO2::linkingSucceeded, this, &QgsAuthOAuth2Method::onLinkingSucceeded, Qt::UniqueConnection );
|
connect( o2, &QgsO2::linkingSucceeded, this, &QgsAuthOAuth2Method::onLinkingSucceeded, Qt::UniqueConnection );
|
||||||
connect( o2, &QgsO2::openBrowser, this, &QgsAuthOAuth2Method::onOpenBrowser, Qt::UniqueConnection );
|
connect( o2, &QgsO2::openBrowser, this, &QgsAuthOAuth2Method::onOpenBrowser, Qt::UniqueConnection );
|
||||||
connect( o2, &QgsO2::closeBrowser, this, &QgsAuthOAuth2Method::onCloseBrowser, 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;
|
//qRegisterMetaType<QNetworkReply::NetworkError>( QStringLiteral( "QNetworkReply::NetworkError" )) // for Qt::QueuedConnection, if needed;
|
||||||
connect( o2, &QgsO2::refreshFinished, this, &QgsAuthOAuth2Method::onRefreshFinished, Qt::UniqueConnection );
|
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,
|
bool QgsAuthOAuth2Method::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
|
||||||
const QString &dataprovider )
|
const QString &dataprovider )
|
||||||
{
|
{
|
||||||
|
@ -71,22 +71,36 @@ class QgsAuthOAuth2Method : public QgsAuthMethod
|
|||||||
|
|
||||||
//! Triggered when linked condition has changed
|
//! Triggered when linked condition has changed
|
||||||
void onLinkedChanged();
|
void onLinkedChanged();
|
||||||
|
|
||||||
//! Triggered when linking operation failed
|
//! Triggered when linking operation failed
|
||||||
void onLinkingFailed();
|
void onLinkingFailed();
|
||||||
|
|
||||||
//! Triggered when linking operation succeeded
|
//! Triggered when linking operation succeeded
|
||||||
void onLinkingSucceeded();
|
void onLinkingSucceeded();
|
||||||
|
|
||||||
//! Triggered when the browser needs to be opened at \a url
|
//! Triggered when the browser needs to be opened at \a url
|
||||||
void onOpenBrowser( const QUrl &url );
|
void onOpenBrowser( const QUrl &url );
|
||||||
|
|
||||||
//! Triggered on browser close
|
//! Triggered on browser close
|
||||||
void onCloseBrowser();
|
void onCloseBrowser();
|
||||||
|
|
||||||
//! Triggered on reply finished
|
//! Triggered on reply finished
|
||||||
void onReplyFinished();
|
void onReplyFinished();
|
||||||
|
|
||||||
//! Triggered on network error
|
//! Triggered on network error
|
||||||
void onNetworkError( QNetworkReply::NetworkError err );
|
void onNetworkError( QNetworkReply::NetworkError err );
|
||||||
|
|
||||||
//! Triggered on refresh finished
|
//! Triggered on refresh finished
|
||||||
void onRefreshFinished( QNetworkReply::NetworkError err );
|
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:
|
private:
|
||||||
QString mTempStorePath;
|
QString mTempStorePath;
|
||||||
|
|
||||||
|
@ -144,6 +144,12 @@ void QgsO2::clearProperties()
|
|||||||
// TODO: clear object properties
|
// TODO: clear object properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsO2::onSetAuthCode(const QString& code)
|
||||||
|
{
|
||||||
|
setCode( code );
|
||||||
|
onVerificationReceived( QMap<QString, QString>() );
|
||||||
|
}
|
||||||
|
|
||||||
void QgsO2::link()
|
void QgsO2::link()
|
||||||
{
|
{
|
||||||
QgsDebugMsgLevel( QStringLiteral( "QgsO2::link" ), 4 );
|
QgsDebugMsgLevel( QStringLiteral( "QgsO2::link" ), 4 );
|
||||||
|
@ -63,6 +63,9 @@ class QgsO2: public O2
|
|||||||
//! Clear all properties
|
//! Clear all properties
|
||||||
void clearProperties();
|
void clearProperties();
|
||||||
|
|
||||||
|
//! Triggered when auth code was set
|
||||||
|
void onSetAuthCode(const QString &code);
|
||||||
|
|
||||||
//! Authenticate.
|
//! Authenticate.
|
||||||
void link() override;
|
void link() override;
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "qgsapplication.h"
|
#include "qgsapplication.h"
|
||||||
#include "qgsauthmanager.h"
|
#include "qgsauthmanager.h"
|
||||||
#include "qgsauthoauth2config.h"
|
#include "qgsauthoauth2config.h"
|
||||||
|
#include "qgsauthoauth2edit.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,6 +48,9 @@ class TestQgsAuthOAuth2Method: public QObject
|
|||||||
void testOAuth2Config();
|
void testOAuth2Config();
|
||||||
void testOAuth2ConfigIO();
|
void testOAuth2ConfigIO();
|
||||||
void testOAuth2ConfigUtils();
|
void testOAuth2ConfigUtils();
|
||||||
|
void testDynamicRegistration();
|
||||||
|
void testDynamicRegistrationJwt();
|
||||||
|
void testDynamicRegistrationNoEndpoint();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QgsAuthOAuth2Config *baseConfig( bool loaded = false );
|
QgsAuthOAuth2Config *baseConfig( bool loaded = false );
|
||||||
@ -56,8 +60,13 @@ class TestQgsAuthOAuth2Method: public QObject
|
|||||||
QByteArray baseVariantTxt();
|
QByteArray baseVariantTxt();
|
||||||
|
|
||||||
static QString smHashes;
|
static QString smHashes;
|
||||||
|
static QString sTestDataDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
QString TestQgsAuthOAuth2Method::sTestDataDir = QStringLiteral( TEST_DATA_DIR ) + "/auth_system/oauth2";
|
||||||
|
|
||||||
|
|
||||||
QString TestQgsAuthOAuth2Method::smHashes = "#####################";
|
QString TestQgsAuthOAuth2Method::smHashes = "#####################";
|
||||||
//QObject *TestQgsAuthOAuth2Method::smParentObj = new QObject();
|
//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 )
|
QGSTEST_MAIN( TestQgsAuthOAuth2Method )
|
||||||
#include "testqgsauthoauth2method.moc"
|
#include "testqgsauthoauth2method.moc"
|
||||||
|
1
tests/testdata/auth_system/oauth2/auth_code_grant_display_code.jwt
vendored
Normal file
1
tests/testdata/auth_system/oauth2/auth_code_grant_display_code.jwt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJ3d3cuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJhdWQiOiJhcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlIiwic29mdHdhcmVfaWQiOiIwZTNlZmYyMi01NjkwLTQzMDYtYWFmYi04ZDRkMGY1Yjk2N2IiLCJzb2Z0d2FyZV92ZXJzaW9uIjoiMi4xOC40eCIsImNsaWVudF9uYW1lIjoiUUdJUyBPQXV0aDIgUGx1Z2luIiwiY2xpZW50X3VyaSI6Imh0dHBzOi8vcWdpcy5vcmciLCJyZWRpcmVjdF91cmlzIjpbImh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS9vYXV0aC9kaXNwbGF5X2NvZGUucGhwIl0sInRva2VuX2VuZHBvaW50X2F1dGhfbWV0aG9kIjoiY2xpZW50X3NlY3JldF9wb3N0IiwiZ3JhbnRfdHlwZXMiOlsiYXV0aG9yaXphdGlvbl9jb2RlIl0sInJlc3BvbnNlX3R5cGVzIjpbImNvZGUiXSwibG9nb191cmkiOiJodHRwczovL2h1Yi5xZ2lzLm9yZy9hdHRhY2htZW50cy80MDAzL1FHaXNfTG9nby5wbmciLCJzY29wZSI6Im9wZW5pZCBiZWVsZCIsImNvbnRhY3RzIjpbIm1haWx0bzphbUBzZWN1cmUtZGltZW5zaW9ucy5kZSJdLCJ0b3NfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtdG9zLmh0bWwiLCJwb2xpY3lfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtcG9saWN5Lmh0bWwiLCJqd2tzX3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS8ud2VsbC1rbm93bi9qd2tzLmpzb24iLCJraWQiOiJTRFB1YmxpY0tleSIsImlhdCI6MTQ5OTE1MDY3M30.M4ablN_BfmHjVPYZo_uVrCDYLsaalhF_aRsS-hppT9AJd8DAGZn_xYrsq8FIaKjZhivjKHtjbR8zyFee0HjU6iMP9KgC0N_jPn3o5L2n8IEl7oRJ5zW9V-v2SqBdaXffm7TBx7v8KQ2J4uaoARYWgAkBcVVxKZWX9kLgQfaaoSu2Zk-aoMNqzNU8u2UTADpoCwGjOd10ik0ZdQ6VC2czfAfYEmfYg4UYPQDafKjWWEXUhO9PXzD0piv_NJ9o2oDdc34KBj8brZRDbDfuvIP5dxpaDh8PWhVE4Dd7dEsAzPXEFdXnLTGzQ_5zDj_CcUQqJ0SzkohALGhpPgy51Bjwsw
|
3
tests/testdata/auth_system/oauth2/auth_code_grant_display_code_get_config.json
vendored
Normal file
3
tests/testdata/auth_system/oauth2/auth_code_grant_display_code_get_config.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"registration_endpoint": "http://www.qgis.org/oauth2/register"
|
||||||
|
}
|
1
tests/testdata/auth_system/oauth2/auth_code_grant_display_code_registration_endpoint.jwt
vendored
Normal file
1
tests/testdata/auth_system/oauth2/auth_code_grant_display_code_registration_endpoint.jwt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfbmFtZSI6IlFHSVMgT0F1dGgyIFBsdWdpbiIsInNvZnR3YXJlX3ZlcnNpb24iOiIyLjE4LjR4IiwicG9saWN5X3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS9xZ2lzLXBvbGljeS5odG1sIiwic29mdHdhcmVfaWQiOiIwZTNlZmYyMi01NjkwLTQzMDYtYWFmYi04ZDRkMGY1Yjk2N2IiLCJjbGllbnRfdXJpIjoiaHR0cHM6Ly9xZ2lzLm9yZyIsImdyYW50X3R5cGVzIjpbImF1dGhvcml6YXRpb25fY29kZSJdLCJpc3MiOiJ3d3cuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJyZXNwb25zZV90eXBlcyI6WyJjb2RlIl0sImF1ZCI6ImFzLnRiMTMuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJ0b3NfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtdG9zLmh0bWwiLCJ0b2tlbl9lbmRwb2ludF9hdXRoX21ldGhvZCI6ImNsaWVudF9zZWNyZXRfcG9zdCIsImNvbnRhY3RzIjpbIm1haWx0bzphbUBzZWN1cmUtZGltZW5zaW9ucy5kZSJdLCJqd2tzX3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS8ud2VsbC1rbm93bi9qd2tzLmpzb24iLCJzY29wZSI6Im9wZW5pZCBiZWVsZCIsInJlZGlyZWN0X3VyaXMiOlsiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL29hdXRoL2Rpc3BsYXlfY29kZS5waHAiXSwicmVnaXN0cmF0aW9uX2VuZHBvaW50IjoiaHR0cDovL3d3dy5xZ2lzLm9yZy9vYXV0aDIvcmVnaXN0cmF0aW9uIiwibG9nb191cmkiOiJodHRwczovL2h1Yi5xZ2lzLm9yZy9hdHRhY2htZW50cy80MDAzL1FHaXNfTG9nby5wbmciLCJraWQiOiJTRFB1YmxpY0tleSJ9.kd5lIGXjCYPIgffKton3NaTSdIB8KueYpu48vXbydGY
|
4
tests/testdata/auth_system/oauth2/client_information_registration_response.json
vendored
Normal file
4
tests/testdata/auth_system/oauth2/client_information_registration_response.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"client_id": "___QGIS_ROCKS___@www.qgis.org",
|
||||||
|
"client_secret": "___QGIS_ROCKS______QGIS_ROCKS______QGIS_ROCKS___"
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user