[oauth2] Allow for extra token(s) to be added into the header for header access methods

This commit is contained in:
Mathieu Pellerin 2025-02-19 15:54:46 +07:00 committed by Nyall Dawson
parent 992cdcbf62
commit 2b9406e85a
7 changed files with 307 additions and 6 deletions

View File

@ -53,6 +53,7 @@ QgsAuthOAuth2Config::QgsAuthOAuth2Config( QObject *parent )
connect( this, &QgsAuthOAuth2Config::requestTimeoutChanged, this, &QgsAuthOAuth2Config::configChanged );
connect( this, &QgsAuthOAuth2Config::queryPairsChanged, this, &QgsAuthOAuth2Config::configChanged );
connect( this, &QgsAuthOAuth2Config::customHeaderChanged, this, &QgsAuthOAuth2Config::configChanged );
connect( this, &QgsAuthOAuth2Config::extraTokensChanged, this, &QgsAuthOAuth2Config::configChanged );
// always recheck validity on any change
// this, in turn, may emit validityChanged( bool )
@ -230,6 +231,14 @@ void QgsAuthOAuth2Config::setCustomHeader( const QString &header )
emit customHeaderChanged( mCustomHeader );
}
void QgsAuthOAuth2Config::setExtraTokens( const QVariantMap &tokens )
{
const QVariantMap preval( mExtraTokens );
mExtraTokens = tokens;
if ( preval != tokens )
emit extraTokensChanged( mExtraTokens );
}
void QgsAuthOAuth2Config::setRequestTimeout( int value )
{
const int preval( mRequestTimeout );
@ -269,6 +278,7 @@ void QgsAuthOAuth2Config::setToDefaults()
setPersistToken( false );
setAccessMethod( QgsAuthOAuth2Config::AccessMethod::Header );
setCustomHeader( QString() );
setExtraTokens( QVariantMap() );
setRequestTimeout( 30 ); // in seconds
setQueryPairs( QVariantMap() );
}
@ -381,6 +391,9 @@ bool QgsAuthOAuth2Config::loadConfigTxt(
if ( variantMap.contains( QStringLiteral( "customHeader" ) ) )
setCustomHeader( variantMap.value( QStringLiteral( "customHeader" ) ).toString() );
if ( variantMap.contains( QStringLiteral( "extraTokens" ) ) )
setExtraTokens( variantMap.value( QStringLiteral( "extraTokens" ) ).toMap() );
if ( variantMap.contains( QStringLiteral( "requestTimeout" ) ) )
setRequestTimeout( variantMap.value( QStringLiteral( "requestTimeout" ) ).toInt() );
if ( variantMap.contains( QStringLiteral( "requestUrl" ) ) )
@ -428,6 +441,7 @@ QByteArray QgsAuthOAuth2Config::saveConfigTxt(
variant.insert( "clientSecret", clientSecret() );
variant.insert( "configType", static_cast<int>( configType() ) );
variant.insert( "customHeader", customHeader() );
variant.insert( "extraTokens", extraTokens() );
variant.insert( "description", description() );
variant.insert( "grantFlow", static_cast<int>( grantFlow() ) );
variant.insert( "id", id() );
@ -480,6 +494,7 @@ QVariantMap QgsAuthOAuth2Config::mappedProperties() const
vmap.insert( QStringLiteral( "refreshTokenUrl" ), this->refreshTokenUrl() );
vmap.insert( QStringLiteral( "accessMethod" ), static_cast<int>( this->accessMethod() ) );
vmap.insert( QStringLiteral( "customHeader" ), this->customHeader() );
vmap.insert( QStringLiteral( "extraTokens" ), this->extraTokens() );
vmap.insert( QStringLiteral( "requestTimeout" ), this->requestTimeout() );
vmap.insert( QStringLiteral( "requestUrl" ), this->requestUrl() );
vmap.insert( QStringLiteral( "scope" ), this->scope() );

View File

@ -92,6 +92,7 @@ class QgsAuthOAuth2Config : public QObject
Q_PROPERTY( int requestTimeout READ requestTimeout WRITE setRequestTimeout NOTIFY requestTimeoutChanged )
Q_PROPERTY( QVariantMap queryPairs READ queryPairs WRITE setQueryPairs NOTIFY queryPairsChanged )
Q_PROPERTY( QString customHeader READ customHeader WRITE setCustomHeader NOTIFY customHeaderChanged )
Q_PROPERTY( QVariantMap extraTokens READ extraTokens WRITE setExtraTokens NOTIFY extraTokensChanged )
//! Construct a QgsAuthOAuth2Config instance
explicit QgsAuthOAuth2Config( QObject *parent = nullptr );
@ -165,6 +166,14 @@ class QgsAuthOAuth2Config : public QObject
*/
QString customHeader() const { return mCustomHeader; }
/**
* Returns the extra tokens that will be added into the header for header access methods where
* the map key represents the token name and the associated value the header name to be used.
*
* \since QGIS 3.42
*/
QVariantMap extraTokens() const { return mExtraTokens; }
//! Request timeout
int requestTimeout() const { return mRequestTimeout; }
@ -316,6 +325,14 @@ class QgsAuthOAuth2Config : public QObject
*/
void setCustomHeader( const QString &header );
/**
* Sets the extra \a tokens that will be added into the header for header access methods where
* the map key represents the token name and the associated value the header name to be used.
*
* \since QGIS 3.42
*/
void setExtraTokens( const QVariantMap &tokens );
//! Set request timeout to \a value
void setRequestTimeout( int value );
//! Set query pairs to \a pairs
@ -378,6 +395,12 @@ class QgsAuthOAuth2Config : public QObject
*/
void customHeaderChanged( const QString & );
/**
* Emitted when the extra tokens header list has changed
* \since QGIS 3.42
*/
void extraTokensChanged( const QVariantMap & );
//! Emitted when configuration request timeout has changed
void requestTimeoutChanged( int );
//! Emitted when configuration query pair has changed
@ -407,6 +430,7 @@ class QgsAuthOAuth2Config : public QObject
bool mPersistToken = false;
AccessMethod mAccessMethod = AccessMethod::Header;
QString mCustomHeader;
QVariantMap mExtraTokens;
int mRequestTimeout = 30; // in seconds
QVariantMap mQueryPairs;
bool mValid = false;

View File

@ -322,6 +322,21 @@ bool QgsAuthOAuth2Method::updateNetworkRequest( QNetworkRequest &request, const
{
const QString header = o2->oauth2config()->customHeader().isEmpty() ? QString( O2_HTTP_AUTHORIZATION_HEADER ) : o2->oauth2config()->customHeader();
request.setRawHeader( header.toLatin1(), QStringLiteral( "Bearer %1" ).arg( o2->token() ).toLatin1() );
const QVariantMap extraTokens = o2->oauth2config()->extraTokens();
if ( !extraTokens.isEmpty() )
{
const QVariantMap receivedExtraTokens = o2->extraTokens();
const QStringList extraTokenNames = extraTokens.keys();
for ( const QString &extraTokenName : extraTokenNames )
{
if ( receivedExtraTokens.contains( extraTokenName ) )
{
request.setRawHeader( extraTokens[extraTokenName].toString().replace( '_', '-' ).toLatin1(), receivedExtraTokens[extraTokenName].toString().toLatin1() );
}
}
}
#ifdef QGISDEBUG
msg = QStringLiteral( "Updated request HEADER with access token for authcfg: %1" ).arg( authcfg );
QgsDebugMsgLevel( msg, 2 );

View File

@ -191,6 +191,11 @@ void QgsAuthOAuth2Edit::setupConnections()
connect( spnbxRequestTimeout, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ), mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setRequestTimeout );
connect( mTokenHeaderLineEdit, &QLineEdit::textChanged, mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::setCustomHeader );
connect( mExtraTokensTable, &QTableWidget::cellChanged, mOAuthConfigCustom.get(), [=]( int, int ) {
mOAuthConfigCustom->setExtraTokens( extraTokens() );
} );
connect( mAddExtraTokenButton, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::addExtraToken );
connect( mRemoveExtraTokenButton, &QToolButton::clicked, this, &QgsAuthOAuth2Edit::removeExtraToken );
connect( mOAuthConfigCustom.get(), &QgsAuthOAuth2Config::validityChanged, this, &QgsAuthOAuth2Edit::configValidityChanged );
@ -403,6 +408,8 @@ void QgsAuthOAuth2Edit::loadFromOAuthConfig( const QgsAuthOAuth2Config *config )
leApiKey->setText( config->apiKey() );
mTokenHeaderLineEdit->setText( config->customHeader() );
populateExtraTokens( config->extraTokens() );
// advanced
chkbxTokenPersist->setChecked( config->persistToken() );
cmbbxAccessMethod->setCurrentIndex( static_cast<int>( config->accessMethod() ) );
@ -873,11 +880,19 @@ void QgsAuthOAuth2Edit::updateConfigAccessMethod( int indx )
case QgsAuthOAuth2Config::AccessMethod::Header:
mTokenHeaderLineEdit->setVisible( true );
mTokenHeaderLabel->setVisible( true );
mExtraTokensHeaderLabel->setVisible( true );
mExtraTokensTable->setVisible( true );
mAddExtraTokenButton->setVisible( true );
mAddExtraTokenButton->setVisible( true );
break;
case QgsAuthOAuth2Config::AccessMethod::Form:
case QgsAuthOAuth2Config::AccessMethod::Query:
mTokenHeaderLineEdit->setVisible( false );
mTokenHeaderLabel->setVisible( false );
mExtraTokensHeaderLabel->setVisible( false );
mExtraTokensTable->setVisible( false );
mAddExtraTokenButton->setVisible( false );
mAddExtraTokenButton->setVisible( false );
break;
}
}
@ -915,7 +930,6 @@ void QgsAuthOAuth2Edit::populateQueryPairs( const QVariantMap &querypairs, bool
}
}
void QgsAuthOAuth2Edit::queryTableSelectionChanged()
{
const bool hassel = tblwdgQueryPairs->selectedItems().count() > 0;
@ -941,7 +955,6 @@ QVariantMap QgsAuthOAuth2Edit::queryPairs() const
return querypairs;
}
void QgsAuthOAuth2Edit::addQueryPair()
{
addQueryPairRow( QString(), QString() );
@ -956,7 +969,6 @@ void QgsAuthOAuth2Edit::removeQueryPair()
tblwdgQueryPairs->removeRow( tblwdgQueryPairs->currentRow() );
}
void QgsAuthOAuth2Edit::clearQueryPairs()
{
for ( int i = tblwdgQueryPairs->rowCount(); i > 0; --i )
@ -965,6 +977,78 @@ void QgsAuthOAuth2Edit::clearQueryPairs()
}
}
QVariantMap QgsAuthOAuth2Edit::extraTokens() const
{
QVariantMap extraTokens;
for ( int i = 0; i < mExtraTokensTable->rowCount(); ++i )
{
if ( mExtraTokensTable->item( i, 0 )->text().isEmpty() || mExtraTokensTable->item( i, 1 )->text().isEmpty() )
{
continue;
}
extraTokens.insert( mExtraTokensTable->item( i, 0 )->text(), QVariant( mExtraTokensTable->item( i, 1 )->text() ) );
}
return extraTokens;
}
void QgsAuthOAuth2Edit::addExtraToken()
{
mExtraTokensTable->blockSignals( true );
addExtraTokenRow( QString(), QString() );
mExtraTokensTable->blockSignals( false );
mExtraTokensTable->setFocus();
mExtraTokensTable->setCurrentCell( mExtraTokensTable->rowCount() - 1, 0 );
mExtraTokensTable->edit( mExtraTokensTable->currentIndex() );
}
void QgsAuthOAuth2Edit::removeExtraToken()
{
mExtraTokensTable->removeRow( mExtraTokensTable->currentRow() );
}
void QgsAuthOAuth2Edit::clearExtraTokens()
{
for ( int i = mExtraTokensTable->rowCount(); i > 0; --i )
{
mExtraTokensTable->removeRow( i - 1 );
}
}
void QgsAuthOAuth2Edit::addExtraTokenRow( const QString &key, const QString &val )
{
const int rowCnt = mExtraTokensTable->rowCount();
mExtraTokensTable->insertRow( rowCnt );
const Qt::ItemFlags itmFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable
| Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
QTableWidgetItem *keyItm = new QTableWidgetItem( key );
keyItm->setFlags( itmFlags );
mExtraTokensTable->setItem( rowCnt, 0, keyItm );
QTableWidgetItem *valItm = new QTableWidgetItem( val );
keyItm->setFlags( itmFlags );
mExtraTokensTable->setItem( rowCnt, 1, valItm );
}
void QgsAuthOAuth2Edit::populateExtraTokens( const QVariantMap &tokens, bool append )
{
mExtraTokensTable->blockSignals( true );
if ( !append )
{
clearExtraTokens();
}
QVariantMap::const_iterator i = tokens.constBegin();
while ( i != tokens.constEnd() )
{
addExtraTokenRow( i.key(), i.value().toString() );
++i;
}
mExtraTokensTable->blockSignals( false );
}
void QgsAuthOAuth2Edit::parseSoftwareStatement( const QString &path )
{
QFile file( path );

View File

@ -90,6 +90,14 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
void populateQueryPairs( const QVariantMap &querypairs, bool append = false );
void addExtraToken();
void removeExtraToken();
void clearExtraTokens();
void populateExtraTokens( const QVariantMap &tokens, bool append = false );
void queryTableSelectionChanged();
void updateConfigQueryPairs();
@ -139,6 +147,9 @@ class QgsAuthOAuth2Edit : public QgsAuthMethodEdit, private Ui::QgsAuthOAuth2Edi
void addQueryPairRow( const QString &key, const QString &val );
QVariantMap queryPairs() const;
void addExtraTokenRow( const QString &key, const QString &val );
QVariantMap extraTokens() const;
int customTab() const { return 0; }
int definedTab() const { return 1; }
int statementTab() const { return 2; }

View File

@ -253,7 +253,7 @@
</property>
</widget>
</item>
<item row="20" column="1">
<item row="21" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -366,7 +366,7 @@
</property>
</widget>
</item>
<item row="19" column="0">
<item row="20" column="0">
<widget class="QLabel" name="lblRequestTimeout">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
@ -523,7 +523,7 @@
</property>
</widget>
</item>
<item row="19" column="1">
<item row="20" column="1">
<widget class="QSpinBox" name="spnbxRequestTimeout">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@ -637,6 +637,146 @@
</property>
</widget>
</item>
<item row="19" column="0">
<widget class="QLabel" name="mExtraTokensHeaderLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Extra token(s) header</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="19" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>3</number>
</property>
<item>
<widget class="QTableWidget" name="mExtraTokensTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>70</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AllEditTriggers</set>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragOnly</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>120</number>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>120</number>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Token</string>
</property>
</column>
<column>
<property name="text">
<string>Header name</string>
</property>
</column>
<property name="toolTip">
<string>These extra tokens will be included in the request headers sent to the resource when provided by the token endpoint</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QToolButton" name="mAddExtraTokenButton">
<property name="minimumSize">
<size>
<width>24</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string notr="true">+</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="mRemoveExtraTokenButton">
<property name="minimumSize">
<size>
<width>24</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string notr="true"></string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
@ -892,6 +1032,7 @@
<number>6</number>
</property>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="spacing">
<number>3</number>
@ -999,6 +1140,7 @@
</layout>
</item>
</layout>
</item>
</layout>
</widget>

View File

@ -125,6 +125,9 @@ QgsAuthOAuth2Config *TestQgsAuthOAuth2Method::baseConfig( bool loaded )
config->setAccessMethod( QgsAuthOAuth2Config::AccessMethod::Header );
config->setCustomHeader( QStringLiteral( "x-auth" ) );
config->setRequestTimeout( 30 ); // in seconds
QVariantMap extraTokens;
extraTokens.insert( "id_token", "X-QGS-OPENID" );
config->setExtraTokens( extraTokens );
QVariantMap queryPairs;
queryPairs.insert( "pf.username", "myusername" );
queryPairs.insert( "pf.password", "mypassword" );
@ -147,6 +150,9 @@ QByteArray TestQgsAuthOAuth2Method::baseConfigTxt( bool pretty )
" \"configType\": 1,\n"
" \"customHeader\": \"x-auth\",\n"
" \"description\": \"A test config\",\n"
" \"extraTokens\": {\n"
" \"id_token\": \"X-QGS-OPENID\"\n"
" },\n"
" \"grantFlow\": 0,\n"
" \"id\": \"abc1234\",\n"
" \"name\": \"MyConfig\",\n"
@ -178,6 +184,7 @@ QByteArray TestQgsAuthOAuth2Method::baseConfigTxt( bool pretty )
"\"configType\":1,"
"\"customHeader\":\"x-auth\","
"\"description\":\"A test config\","
"\"extraTokens\":{\"id_token\":\"X-QGS-OPENID\"},"
"\"grantFlow\":0,"
"\"id\":\"abc1234\","
"\"name\":\"MyConfig\","
@ -215,6 +222,9 @@ QVariantMap TestQgsAuthOAuth2Method::baseVariantMap()
vmap.insert( "password", "mypassword" );
vmap.insert( "persistToken", false );
vmap.insert( "customHeader", "x-auth" );
QVariantMap extraTokens;
extraTokens.insert( "id_token", "X-QGS-OPENID" );
vmap.insert( "extraTokens", extraTokens );
QVariantMap qpairs;
qpairs.insert( "pf.password", "mypassword" );
qpairs.insert( "pf.username", "myusername" );