Skip to content

Commit a9f924f

Browse files
authoredJul 24, 2018
Merge pull request #7441 from elpaso/oauth2-testbed13-client-registration
[oauth] JWT client registration
2 parents e6b1e48 + fbe05be commit a9f924f

12 files changed

+529
-39
lines changed
 

‎src/auth/oauth2/qgsauthoauth2edit.cpp

Lines changed: 263 additions & 34 deletions
Large diffs are not rendered by default.

‎src/auth/oauth2/qgsauthoauth2edit.h

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define QGSAUTHOAUTH2EDIT_H
1717

1818
#include <QWidget>
19+
#include <QNetworkReply>
1920
#include "qgsauthmethodedit.h"
2021
#include "ui_qgsauthoauth2edit.h"
2122

@@ -49,6 +50,7 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
4950
*/
5051
QgsStringMap configMap() const override;
5152

53+
5254
public slots:
5355

5456
//! Load the configuration from \a configMap
@@ -68,30 +70,42 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
6870
void removeTokenCacheFile();
6971

7072
void populateGrantFlows();
73+
7174
void updateGrantFlow( int indx );
7275

7376
void exportOAuthConfig();
77+
7478
void importOAuthConfig();
7579

7680
void descriptionChanged();
7781

7882
void populateAccessMethods();
83+
7984
void updateConfigAccessMethod( int indx );
8085

8186
void addQueryPair();
87+
8288
void removeQueryPair();
89+
8390
void clearQueryPairs();
91+
8492
void populateQueryPairs( const QVariantMap &querypairs, bool append = false );
93+
8594
void queryTableSelectionChanged();
95+
8696
void updateConfigQueryPairs();
8797

8898
void updateDefinedConfigsCache();
99+
89100
void loadDefinedConfigs();
101+
90102
void setCurrentDefinedConfig( const QString &id );
103+
91104
void currentDefinedItemChanged( QListWidgetItem *cur, QListWidgetItem *prev );
105+
92106
void selectCurrentDefinedConfig();
93107

94-
void loadFromOAuthConfig( const QgsAuthOAuth2Config *config = nullptr );
108+
void getSoftStatementDir();
95109

96110
void updateTokenCacheFile( bool curpersist ) const;
97111

@@ -101,8 +115,20 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
101115

102116
void getDefinedCustomDir();
103117

118+
void loadFromOAuthConfig( const QgsAuthOAuth2Config *config );
119+
120+
void softwareStatementJwtPathChanged( const QString &path );
121+
122+
void configReplyFinished();
123+
124+
void registerReplyFinished();
125+
126+
void networkError( QNetworkReply::NetworkError error );
127+
104128
private:
129+
105130
void initGui();
131+
void parseSoftwareStatement( const QString &path );
106132

107133
QWidget *parentWidget() const;
108134
QLineEdit *parentNameField() const;
@@ -117,8 +143,11 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
117143

118144
int customTab() const { return 0; }
119145
int definedTab() const { return 1; }
146+
int statementTab() const { return 2; }
120147
bool onCustomTab() const;
121148
bool onDefinedTab() const;
149+
bool onStatementTab() const;
150+
void getSoftwareStatementConfig();
122151

123152
QString currentDefinedConfig() const { return mDefinedId; }
124153

@@ -131,6 +160,11 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
131160
int mCurTab = 0;
132161
bool mPrevPersistToken = false;
133162
QToolButton *btnTokenClear = nullptr;
163+
QString mRegistrationEndpoint;
164+
QMap<QString, QVariant> mSoftwareStatement;
165+
void registerSoftStatement( const QString &registrationUrl );
166+
bool mDownloading = false;
167+
friend class TestQgsAuthOAuth2Method;
134168
};
135169

136170
#endif // QGSAUTHOAUTH2EDIT_H

‎src/auth/oauth2/qgsauthoauth2edit.ui

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
</sizepolicy>
7676
</property>
7777
<property name="currentIndex">
78-
<number>0</number>
78+
<number>2</number>
7979
</property>
8080
<widget class="QWidget" name="tabCustom">
8181
<attribute name="title">
@@ -202,7 +202,7 @@
202202
<property name="geometry">
203203
<rect>
204204
<x>0</x>
205-
<y>-241</y>
205+
<y>0</y>
206206
<width>401</width>
207207
<height>516</height>
208208
</rect>
@@ -734,6 +734,85 @@
734734
</item>
735735
</layout>
736736
</widget>
737+
<widget class="QWidget" name="tabStatement">
738+
<attribute name="title">
739+
<string>Software Statement</string>
740+
</attribute>
741+
<layout class="QVBoxLayout" name="verticalLayout_7">
742+
<property name="spacing">
743+
<number>6</number>
744+
</property>
745+
<property name="sizeConstraint">
746+
<enum>QLayout::SetDefaultConstraint</enum>
747+
</property>
748+
<item>
749+
<layout class="QGridLayout" name="gridLayout_4">
750+
<item row="1" column="0">
751+
<widget class="QLabel" name="lblSoftStatementDir">
752+
<property name="text">
753+
<string>Software Statement</string>
754+
</property>
755+
</widget>
756+
</item>
757+
<item row="0" column="1">
758+
<widget class="QLineEdit" name="leSoftwareStatementConfigUrl">
759+
<property name="placeholderText">
760+
<string>Optional</string>
761+
</property>
762+
</widget>
763+
</item>
764+
<item row="1" column="2">
765+
<widget class="QToolButton" name="btnSoftStatementDir">
766+
<property name="text">
767+
<string>...</string>
768+
</property>
769+
<property name="icon">
770+
<iconset resource="oauth2_resources.qrc">
771+
<normaloff>:/oauth2method/oauth2_resources/fileopen.svg</normaloff>:/oauth2method/oauth2_resources/fileopen.svg</iconset>
772+
</property>
773+
</widget>
774+
</item>
775+
<item row="0" column="0">
776+
<widget class="QLabel" name="lblConfigUrl">
777+
<property name="text">
778+
<string>Configuration Url</string>
779+
</property>
780+
</widget>
781+
</item>
782+
<item row="1" column="1">
783+
<widget class="QLineEdit" name="leSoftwareStatementJwtPath">
784+
<property name="placeholderText">
785+
<string>Required</string>
786+
</property>
787+
</widget>
788+
</item>
789+
<item row="2" column="1">
790+
<widget class="QPushButton" name="btnRegister">
791+
<property name="enabled">
792+
<bool>false</bool>
793+
</property>
794+
<property name="text">
795+
<string>Register</string>
796+
</property>
797+
</widget>
798+
</item>
799+
</layout>
800+
</item>
801+
<item>
802+
<spacer name="verticalSpacer_2">
803+
<property name="orientation">
804+
<enum>Qt::Vertical</enum>
805+
</property>
806+
<property name="sizeHint" stdset="0">
807+
<size>
808+
<width>20</width>
809+
<height>40</height>
810+
</size>
811+
</property>
812+
</spacer>
813+
</item>
814+
</layout>
815+
</widget>
737816
</widget>
738817
<widget class="QgsCollapsibleGroupBoxBasic" name="grpbxAdvanced">
739818
<property name="sizePolicy">

‎src/auth/oauth2/qgsauthoauth2method.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "qgssettings.h"
3030

3131
#include <QDateTime>
32+
#include <QInputDialog>
3233
#include <QDesktopServices>
3334
#include <QDir>
3435
#include <QEventLoop>
@@ -172,7 +173,8 @@ bool QgsAuthOAuth2Method::updateNetworkRequest( QNetworkRequest &request, const
172173
connect( o2, &QgsO2::linkingSucceeded, this, &QgsAuthOAuth2Method::onLinkingSucceeded, Qt::UniqueConnection );
173174
connect( o2, &QgsO2::openBrowser, this, &QgsAuthOAuth2Method::onOpenBrowser, Qt::UniqueConnection );
174175
connect( o2, &QgsO2::closeBrowser, this, &QgsAuthOAuth2Method::onCloseBrowser, Qt::UniqueConnection );
175-
176+
connect( o2, &QgsO2::getAuthCode, this, &QgsAuthOAuth2Method::onAuthCode, Qt::UniqueConnection );
177+
connect( this, &QgsAuthOAuth2Method::setAuthCode, o2, &QgsO2::onSetAuthCode, Qt::UniqueConnection );
176178
//qRegisterMetaType<QNetworkReply::NetworkError>( QStringLiteral( "QNetworkReply::NetworkError" )) // for Qt::QueuedConnection, if needed;
177179
connect( o2, &QgsO2::refreshFinished, this, &QgsAuthOAuth2Method::onRefreshFinished, Qt::UniqueConnection );
178180

@@ -457,6 +459,16 @@ void QgsAuthOAuth2Method::onRefreshFinished( QNetworkReply::NetworkError err )
457459
}
458460
}
459461

462+
void QgsAuthOAuth2Method::onAuthCode()
463+
{
464+
bool ok = false;
465+
QString code = QInputDialog::getText( QApplication::activeWindow(), QStringLiteral( "Enter the authorization code" ), QStringLiteral( "Authoriation code" ), QLineEdit::Normal, QStringLiteral( "Required" ), &ok, Qt::Dialog, Qt::InputMethodHint::ImhNone );
466+
if ( ok && !code.isEmpty() )
467+
{
468+
emit setAuthCode( code );
469+
}
470+
}
471+
460472
bool QgsAuthOAuth2Method::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
461473
const QString &dataprovider )
462474
{

‎src/auth/oauth2/qgsauthoauth2method.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,36 @@ class QgsAuthOAuth2Method : public QgsAuthMethod
7171

7272
//! Triggered when linked condition has changed
7373
void onLinkedChanged();
74+
7475
//! Triggered when linking operation failed
7576
void onLinkingFailed();
77+
7678
//! Triggered when linking operation succeeded
7779
void onLinkingSucceeded();
7880

7981
//! Triggered when the browser needs to be opened at \a url
8082
void onOpenBrowser( const QUrl &url );
83+
8184
//! Triggered on browser close
8285
void onCloseBrowser();
86+
8387
//! Triggered on reply finished
8488
void onReplyFinished();
89+
8590
//! Triggered on network error
8691
void onNetworkError( QNetworkReply::NetworkError err );
92+
8793
//! Triggered on refresh finished
8894
void onRefreshFinished( QNetworkReply::NetworkError err );
8995

96+
//! Triggered when auth code needs to be manually entered by the user
97+
void onAuthCode();
98+
99+
signals:
100+
101+
//! Emitted when authcode was manually set by the user
102+
void setAuthCode( const QString code );
103+
90104
private:
91105
QString mTempStorePath;
92106

‎src/auth/oauth2/qgso2.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ void QgsO2::clearProperties()
144144
// TODO: clear object properties
145145
}
146146

147+
void QgsO2::onSetAuthCode( const QString &code )
148+
{
149+
setCode( code );
150+
onVerificationReceived( QMap<QString, QString>() );
151+
}
152+
147153
void QgsO2::link()
148154
{
149155
QgsDebugMsgLevel( QStringLiteral( "QgsO2::link" ), 4 );
@@ -222,7 +228,7 @@ void QgsO2::link()
222228

223229
QUrl url( tokenUrl_ );
224230
QNetworkRequest tokenRequest( url );
225-
tokenRequest.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
231+
tokenRequest.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1Literal( "application/x-www-form-urlencoded" ) );
226232
QNetworkReply *tokenReply = manager_->post( tokenRequest, payload );
227233

228234
connect( tokenReply, SIGNAL( finished() ), this, SLOT( onTokenReplyFinished() ), Qt::QueuedConnection );

‎src/auth/oauth2/qgso2.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class QgsO2: public O2
6363
//! Clear all properties
6464
void clearProperties();
6565

66+
//! Triggered when auth code was set
67+
void onSetAuthCode( const QString &code );
68+
6669
//! Authenticate.
6770
void link() override;
6871

‎tests/src/auth/testqgsauthoauth2method.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "qgsapplication.h"
2929
#include "qgsauthmanager.h"
3030
#include "qgsauthoauth2config.h"
31+
#include "qgsauthoauth2edit.h"
3132

3233

3334
/**
@@ -47,6 +48,9 @@ class TestQgsAuthOAuth2Method: public QObject
4748
void testOAuth2Config();
4849
void testOAuth2ConfigIO();
4950
void testOAuth2ConfigUtils();
51+
void testDynamicRegistration();
52+
void testDynamicRegistrationJwt();
53+
void testDynamicRegistrationNoEndpoint();
5054

5155
private:
5256
QgsAuthOAuth2Config *baseConfig( bool loaded = false );
@@ -56,8 +60,13 @@ class TestQgsAuthOAuth2Method: public QObject
5660
QByteArray baseVariantTxt();
5761

5862
static QString smHashes;
63+
static QString sTestDataDir;
5964
};
6065

66+
67+
QString TestQgsAuthOAuth2Method::sTestDataDir = QStringLiteral( TEST_DATA_DIR ) + "/auth_system/oauth2";
68+
69+
6170
QString TestQgsAuthOAuth2Method::smHashes = "#####################";
6271
//QObject *TestQgsAuthOAuth2Method::smParentObj = new QObject();
6372

@@ -400,5 +409,100 @@ void TestQgsAuthOAuth2Method::testOAuth2ConfigUtils()
400409

401410
}
402411

412+
void TestQgsAuthOAuth2Method::testDynamicRegistrationNoEndpoint()
413+
{
414+
QgsAuthOAuth2Config *config = baseConfig();
415+
config->setClientId( QString( ) );
416+
config->setClientSecret( QString( ) );
417+
QVariantMap configMap( config->mappedProperties() );
418+
QCOMPARE( configMap["clientId"].toString(), QString() );
419+
QCOMPARE( configMap["clientSecret"].toString(), QString() );
420+
QgsAuthOAuth2Edit dlg;
421+
QgsStringMap stringMap;
422+
for ( const auto &k : configMap.keys( ) )
423+
{
424+
stringMap[k] = configMap.value( k ).toString();
425+
}
426+
dlg.loadConfig( stringMap );
427+
QCOMPARE( dlg.leClientId->text(), QString() );
428+
QCOMPARE( dlg.leClientSecret->text(), QString() );
429+
430+
// This JWT does not contain a registration_endpoint
431+
dlg.leSoftwareStatementJwtPath->setText( QStringLiteral( "%1/auth_code_grant_display_code.jwt" ).arg( sTestDataDir ) );
432+
QVERIFY( ! dlg.btnRegister->isEnabled() );
433+
QCOMPARE( dlg.leSoftwareStatementConfigUrl->text(), QString() );
434+
}
435+
436+
void TestQgsAuthOAuth2Method::testDynamicRegistration()
437+
{
438+
QgsAuthOAuth2Config *config = baseConfig();
439+
config->setClientId( QString( ) );
440+
config->setClientSecret( QString( ) );
441+
QVariantMap configMap( config->mappedProperties() );
442+
QCOMPARE( configMap["clientId"].toString(), QString() );
443+
QCOMPARE( configMap["clientSecret"].toString(), QString() );
444+
QgsAuthOAuth2Edit dlg;
445+
QgsStringMap stringMap;
446+
for ( const auto &k : configMap.keys( ) )
447+
{
448+
stringMap[k] = configMap.value( k ).toString();
449+
}
450+
dlg.loadConfig( stringMap );
451+
QCOMPARE( dlg.leClientId->text(), QString() );
452+
QCOMPARE( dlg.leClientSecret->text(), QString() );
453+
454+
// This JWT does not contain a registration_endpoint
455+
dlg.leSoftwareStatementJwtPath->setText( QStringLiteral( "%1/auth_code_grant_display_code.jwt" ).arg( sTestDataDir ) );
456+
QVERIFY( ! dlg.btnRegister->isEnabled() );
457+
QCOMPARE( dlg.leSoftwareStatementConfigUrl->text(), QString() );
458+
// Set the config url to something local
459+
dlg.leSoftwareStatementConfigUrl->setText( QUrl::fromLocalFile( QStringLiteral( "%1/auth_code_grant_display_code_get_config.json" ).arg( sTestDataDir ) ).toString( ) );
460+
QVERIFY( dlg.btnRegister->isEnabled() );
461+
// Change it to something local
462+
dlg.mRegistrationEndpoint = QUrl::fromLocalFile( QStringLiteral( "%1/client_information_registration_response.json" ).arg( sTestDataDir ) ).toString();
463+
QTest::mouseClick( dlg.btnRegister, Qt::MouseButton::LeftButton );
464+
while ( dlg.mDownloading )
465+
{
466+
qApp->processEvents();
467+
}
468+
QCOMPARE( dlg.leClientId->text(), QLatin1Literal( "___QGIS_ROCKS___@www.qgis.org" ) );
469+
QCOMPARE( dlg.leClientSecret->text(), QLatin1Literal( "___QGIS_ROCKS______QGIS_ROCKS______QGIS_ROCKS___" ) );
470+
}
471+
472+
473+
void TestQgsAuthOAuth2Method::testDynamicRegistrationJwt()
474+
{
475+
QgsAuthOAuth2Config *config = baseConfig();
476+
config->setClientId( QString( ) );
477+
config->setClientSecret( QString( ) );
478+
QVariantMap configMap( config->mappedProperties() );
479+
QCOMPARE( configMap["clientId"].toString(), QString() );
480+
QCOMPARE( configMap["clientSecret"].toString(), QString() );
481+
QgsAuthOAuth2Edit dlg;
482+
QgsStringMap stringMap;
483+
for ( const auto &k : configMap.keys( ) )
484+
{
485+
stringMap[k] = configMap.value( k ).toString();
486+
}
487+
dlg.loadConfig( stringMap );
488+
QCOMPARE( dlg.leClientId->text(), QString() );
489+
QCOMPARE( dlg.leClientSecret->text(), QString() );
490+
491+
// Now set the config URL to the JWT that does contain a registration_endpoint
492+
dlg.leSoftwareStatementJwtPath->setText( QStringLiteral( "%1/auth_code_grant_display_code_registration_endpoint.jwt" ).arg( sTestDataDir ) );
493+
QCOMPARE( dlg.leSoftwareStatementConfigUrl->text(), QStringLiteral( "http://www.qgis.org/oauth2/registration" ) );
494+
QVERIFY( dlg.btnRegister->isEnabled() );
495+
// Change it to something local
496+
dlg.mRegistrationEndpoint = QUrl::fromLocalFile( QStringLiteral( "%1/client_information_registration_response.json" ).arg( sTestDataDir ) ).toString();
497+
QTest::mouseClick( dlg.btnRegister, Qt::MouseButton::LeftButton );
498+
while ( dlg.mDownloading )
499+
{
500+
qApp->processEvents();
501+
}
502+
QCOMPARE( dlg.leClientId->text(), QLatin1Literal( "___QGIS_ROCKS___@www.qgis.org" ) );
503+
QCOMPARE( dlg.leClientSecret->text(), QLatin1Literal( "___QGIS_ROCKS______QGIS_ROCKS______QGIS_ROCKS___" ) );
504+
}
505+
506+
403507
QGSTEST_MAIN( TestQgsAuthOAuth2Method )
404508
#include "testqgsauthoauth2method.moc"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJ3d3cuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJhdWQiOiJhcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlIiwic29mdHdhcmVfaWQiOiIwZTNlZmYyMi01NjkwLTQzMDYtYWFmYi04ZDRkMGY1Yjk2N2IiLCJzb2Z0d2FyZV92ZXJzaW9uIjoiMi4xOC40eCIsImNsaWVudF9uYW1lIjoiUUdJUyBPQXV0aDIgUGx1Z2luIiwiY2xpZW50X3VyaSI6Imh0dHBzOi8vcWdpcy5vcmciLCJyZWRpcmVjdF91cmlzIjpbImh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS9vYXV0aC9kaXNwbGF5X2NvZGUucGhwIl0sInRva2VuX2VuZHBvaW50X2F1dGhfbWV0aG9kIjoiY2xpZW50X3NlY3JldF9wb3N0IiwiZ3JhbnRfdHlwZXMiOlsiYXV0aG9yaXphdGlvbl9jb2RlIl0sInJlc3BvbnNlX3R5cGVzIjpbImNvZGUiXSwibG9nb191cmkiOiJodHRwczovL2h1Yi5xZ2lzLm9yZy9hdHRhY2htZW50cy80MDAzL1FHaXNfTG9nby5wbmciLCJzY29wZSI6Im9wZW5pZCBiZWVsZCIsImNvbnRhY3RzIjpbIm1haWx0bzphbUBzZWN1cmUtZGltZW5zaW9ucy5kZSJdLCJ0b3NfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtdG9zLmh0bWwiLCJwb2xpY3lfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtcG9saWN5Lmh0bWwiLCJqd2tzX3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS8ud2VsbC1rbm93bi9qd2tzLmpzb24iLCJraWQiOiJTRFB1YmxpY0tleSIsImlhdCI6MTQ5OTE1MDY3M30.M4ablN_BfmHjVPYZo_uVrCDYLsaalhF_aRsS-hppT9AJd8DAGZn_xYrsq8FIaKjZhivjKHtjbR8zyFee0HjU6iMP9KgC0N_jPn3o5L2n8IEl7oRJ5zW9V-v2SqBdaXffm7TBx7v8KQ2J4uaoARYWgAkBcVVxKZWX9kLgQfaaoSu2Zk-aoMNqzNU8u2UTADpoCwGjOd10ik0ZdQ6VC2czfAfYEmfYg4UYPQDafKjWWEXUhO9PXzD0piv_NJ9o2oDdc34KBj8brZRDbDfuvIP5dxpaDh8PWhVE4Dd7dEsAzPXEFdXnLTGzQ_5zDj_CcUQqJ0SzkohALGhpPgy51Bjwsw
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"registration_endpoint": "http://www.qgis.org/oauth2/register"
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfbmFtZSI6IlFHSVMgT0F1dGgyIFBsdWdpbiIsInNvZnR3YXJlX3ZlcnNpb24iOiIyLjE4LjR4IiwicG9saWN5X3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS9xZ2lzLXBvbGljeS5odG1sIiwic29mdHdhcmVfaWQiOiIwZTNlZmYyMi01NjkwLTQzMDYtYWFmYi04ZDRkMGY1Yjk2N2IiLCJjbGllbnRfdXJpIjoiaHR0cHM6Ly9xZ2lzLm9yZyIsImdyYW50X3R5cGVzIjpbImF1dGhvcml6YXRpb25fY29kZSJdLCJpc3MiOiJ3d3cuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJyZXNwb25zZV90eXBlcyI6WyJjb2RlIl0sImF1ZCI6ImFzLnRiMTMuc2VjdXJlLWRpbWVuc2lvbnMuZGUiLCJ0b3NfdXJpIjoiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL3FnaXMtdG9zLmh0bWwiLCJ0b2tlbl9lbmRwb2ludF9hdXRoX21ldGhvZCI6ImNsaWVudF9zZWNyZXRfcG9zdCIsImNvbnRhY3RzIjpbIm1haWx0bzphbUBzZWN1cmUtZGltZW5zaW9ucy5kZSJdLCJqd2tzX3VyaSI6Imh0dHBzOi8vYXMudGIxMy5zZWN1cmUtZGltZW5zaW9ucy5kZS8ud2VsbC1rbm93bi9qd2tzLmpzb24iLCJzY29wZSI6Im9wZW5pZCBiZWVsZCIsInJlZGlyZWN0X3VyaXMiOlsiaHR0cHM6Ly9hcy50YjEzLnNlY3VyZS1kaW1lbnNpb25zLmRlL29hdXRoL2Rpc3BsYXlfY29kZS5waHAiXSwicmVnaXN0cmF0aW9uX2VuZHBvaW50IjoiaHR0cDovL3d3dy5xZ2lzLm9yZy9vYXV0aDIvcmVnaXN0cmF0aW9uIiwibG9nb191cmkiOiJodHRwczovL2h1Yi5xZ2lzLm9yZy9hdHRhY2htZW50cy80MDAzL1FHaXNfTG9nby5wbmciLCJraWQiOiJTRFB1YmxpY0tleSJ9.kd5lIGXjCYPIgffKton3NaTSdIB8KueYpu48vXbydGY
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"client_id": "___QGIS_ROCKS___@www.qgis.org",
3+
"client_secret": "___QGIS_ROCKS______QGIS_ROCKS______QGIS_ROCKS___"
4+
}

0 commit comments

Comments
 (0)
Please sign in to comment.