Skip to content

Commit

Permalink
[authentication] Add a pair of APIs to export and import configuratio…
Browse files Browse the repository at this point in the history
…ns to/from XML
  • Loading branch information
nirvn committed May 12, 2021
1 parent abf5c97 commit f916c06
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 0 deletions.
23 changes: 23 additions & 0 deletions python/core/auto_generated/auth/qgsauthconfig.sip.in
Expand Up @@ -11,6 +11,7 @@




class QgsAuthMethodConfig
{
%Docstring(signature="appended")
Expand Down Expand Up @@ -184,6 +185,28 @@ against the config's :py:func:`~QgsAuthMethodConfig.uri` for auto-selecting auth
:param accessurl: A URL to process
:param resource: Output variable for result
:param withpath: Whether to include the URI's path in output
%End

bool writeXml( QDomElement &parentElement, QDomDocument &document );
%Docstring
Stores the configuration in a DOM

:param parentElement: parent DOM element
:param document: DOM document

.. seealso:: :py:func:`readXml`

.. versionadded:: 3.20
%End

bool readXml( const QDomElement &element );
%Docstring
from a DOM element.

:param element: is the DOM node corresponding to item (e.g. 'LayoutItem' element)
:param document: DOM document

.. versionadded:: 3.20
%End

};
Expand Down
17 changes: 17 additions & 0 deletions python/core/auto_generated/auth/qgsauthmanager.sip.in
Expand Up @@ -298,6 +298,23 @@ Remove an authentication config in the database
:param authcfg: Associated authentication config id

:return: Whether operation succeeded
%End

bool exportAuthenticationConfigsToXml( const QString &filename, const QStringList &authcfgs, const QString &password = QString() );
%Docstring
Export authentication configurations to an XML file

:param filename: The file path to save the XML content to
:param authcfgs: The list of configuration IDs to export
:param password: A password string to encrypt the XML content
%End

bool importAuthenticationConfigsFromXml( const QString &filename, const QString &password = QString() );
%Docstring
Import authentication configurations from an XML file

:param filename: The file path from which the XML content will be read
:param password: A password string to decrypt the XML content
%End

bool removeAllAuthenticationConfigs();
Expand Down
28 changes: 28 additions & 0 deletions src/core/auth/qgsauthconfig.cpp
Expand Up @@ -157,6 +157,34 @@ bool QgsAuthMethodConfig::uriToResource( const QString &accessurl, QString *reso
}


bool QgsAuthMethodConfig::writeXml( QDomElement &parentElement, QDomDocument &document )
{
QDomElement element = document.createElement( QStringLiteral( "AuthMethodConfig" ) );
element.setAttribute( QStringLiteral( "method" ), mMethod );
element.setAttribute( QStringLiteral( "id" ), mId );
element.setAttribute( QStringLiteral( "name" ), mName );
element.setAttribute( QStringLiteral( "version" ), QString::number( mVersion ) );
element.setAttribute( QStringLiteral( "uri" ), mUri );
element.setAttribute( QStringLiteral( "config" ), configString() );
parentElement.appendChild( element );
return true;
}

bool QgsAuthMethodConfig::readXml( const QDomElement &element )
{
if ( element.nodeName() != QLatin1String( "AuthMethodConfig" ) )
return false;

mMethod = element.attribute( QStringLiteral( "method" ) );
mId = element.attribute( QStringLiteral( "id" ) );
mName = element.attribute( QStringLiteral( "name" ) );
mVersion = element.attribute( QStringLiteral( "version" ) ).toInt();
mUri = element.attribute( QStringLiteral( "uri" ) );
loadConfigString( element.attribute( QStringLiteral( "config" ) ) );

return true;
}

#ifndef QT_NO_SSL

//////////////////////////////////////////////////////
Expand Down
20 changes: 20 additions & 0 deletions src/core/auth/qgsauthconfig.h
Expand Up @@ -18,8 +18,11 @@
#define QGSAUTHCONFIG_H

#include "qgis_core.h"

#include <QHash>
#include <QString>
#include <QDomElement>
#include <QDomDocument>

#ifndef QT_NO_SSL
#include <QSslCertificate>
Expand Down Expand Up @@ -160,6 +163,23 @@ class CORE_EXPORT QgsAuthMethodConfig
*/
static bool uriToResource( const QString &accessurl, QString *resource, bool withpath = false );

/**
* Stores the configuration in a DOM
* \param parentElement parent DOM element
* \param document DOM document
* \see readXml()
* \since QGIS 3.20
*/
bool writeXml( QDomElement &parentElement, QDomDocument &document );

/**
* from a DOM element.
* \param element is the DOM node corresponding to item (e.g. 'LayoutItem' element)
* \param document DOM document
* \since QGIS 3.20
*/
bool readXml( const QDomElement &element );

private:
QString mId;
QString mName;
Expand Down
114 changes: 114 additions & 0 deletions src/core/auth/qgsauthmanager.cpp
Expand Up @@ -31,6 +31,8 @@
#include <QTimer>
#include <QVariant>
#include <QSqlDriver>
#include <QDomElement>
#include <QDomDocument>

#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#include <QRandomGenerator>
Expand Down Expand Up @@ -1323,6 +1325,118 @@ bool QgsAuthManager::removeAuthenticationConfig( const QString &authcfg )
return true;
}

bool QgsAuthManager::exportAuthenticationConfigsToXml( const QString &filename, const QStringList &authcfgs, const QString &password )
{
if ( filename.isEmpty() )
return false;

QDomDocument document( QStringLiteral( "qgis_authentication" ) );
QDomElement root = document.createElement( QStringLiteral( "qgis_authentication" ) );
document.appendChild( root );

QString civ;
if ( !password.isEmpty() )
{
QString salt;
QString hash;
QgsAuthCrypto::passwordKeyHash( password, &salt, &hash, &civ );
root.setAttribute( QStringLiteral( "salt" ), salt );
root.setAttribute( QStringLiteral( "hash" ), hash );
root.setAttribute( QStringLiteral( "civ" ), civ );
}

QDomElement configurations = document.createElement( QStringLiteral( "configurations" ) );
for ( const QString &authcfg : authcfgs )
{
QgsAuthMethodConfig authMethodConfig;

bool ok = loadAuthenticationConfig( authcfg, authMethodConfig, true );
if ( ok )
{
authMethodConfig.writeXml( configurations, document );
}
}
if ( !password.isEmpty() )
{
QString configurationsString;
QTextStream ts( &configurationsString );
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
ts.setCodec( "UTF-8" );
#endif
configurations.save( ts, 2 );
root.appendChild( document.createTextNode( QgsAuthCrypto::encrypt( password, civ, configurationsString ) ) );
}
else
{
root.appendChild( configurations );
}

QFile file( filename );
if ( !file.open( QFile::WriteOnly | QIODevice::Truncate ) )
return false;

QTextStream ts( &file );
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
ts.setCodec( "UTF-8" );
#endif
document.save( ts, 2 );
file.close();
return true;
}

bool QgsAuthManager::importAuthenticationConfigsFromXml( const QString &filename, const QString &password )
{
QFile f( filename );
if ( !f.open( QFile::ReadOnly ) )
{
return false;
}

QDomDocument document( QStringLiteral( "qgis_authentication" ) );
if ( !document.setContent( &f ) )
{
f.close();
return false;
}
f.close();

QDomElement root = document.documentElement();
if ( root.tagName() != QLatin1String( "qgis_authentication" ) )
{
return false;
}

QDomElement configurations;
if ( root.hasAttribute( QStringLiteral( "salt" ) ) )
{
QString salt = root.attribute( QStringLiteral( "salt" ) );
QString hash = root.attribute( QStringLiteral( "hash" ) );
QString civ = root.attribute( QStringLiteral( "civ" ) );
if ( !QgsAuthCrypto::verifyPasswordKeyHash( password, salt, hash ) )
return false;

document.setContent( QgsAuthCrypto::decrypt( password, civ, root.text() ) );
configurations = document.firstChild().toElement();
}
else
{
configurations = root.firstChildElement( QStringLiteral( "configurations" ) );
}

QDomElement configuration = configurations.firstChildElement();
while ( !configuration.isNull() )
{
QgsAuthMethodConfig authMethodConfig;
authMethodConfig.readXml( configuration );
qDebug() << configuration.nodeName();
qDebug() << authMethodConfig.id();
storeAuthenticationConfig( authMethodConfig );

configuration = configuration.nextSiblingElement();
}
return true;
}

bool QgsAuthManager::removeAllAuthenticationConfigs()
{
QMutexLocker locker( mMutex.get() );
Expand Down
15 changes: 15 additions & 0 deletions src/core/auth/qgsauthmanager.h
Expand Up @@ -300,6 +300,21 @@ class CORE_EXPORT QgsAuthManager : public QObject
*/
bool removeAuthenticationConfig( const QString &authcfg );

/**
* Export authentication configurations to an XML file
* \param filename The file path to save the XML content to
* \param authcfgs The list of configuration IDs to export
* \param password A password string to encrypt the XML content
*/
bool exportAuthenticationConfigsToXml( const QString &filename, const QStringList &authcfgs, const QString &password = QString() );

/**
* Import authentication configurations from an XML file
* \param filename The file path from which the XML content will be read
* \param password A password string to decrypt the XML content
*/
bool importAuthenticationConfigsFromXml( const QString &filename, const QString &password = QString() );

/**
* Clear all authentication configs from table in database and from provider caches
* \returns Whether operation succeeded
Expand Down
15 changes: 15 additions & 0 deletions tests/src/core/testqgsauthmanager.cpp
Expand Up @@ -323,6 +323,21 @@ void TestQgsAuthManager::testAuthConfigs()
QVERIFY( authm->storeAuthenticationConfig( config ) );
idcfgmap.insert( config.id(), config );
}

QCOMPARE( authm->availableAuthMethodConfigs().size(), 3 );

// Password-less export / import
QVERIFY( authm->exportAuthenticationConfigsToXml( mTempDir + QStringLiteral( "/configs.xml" ), idcfgmap.keys() ) );
QVERIFY( authm->removeAllAuthenticationConfigs() );
QVERIFY( authm->importAuthenticationConfigsFromXml( mTempDir + QStringLiteral( "/configs.xml" ) ) );

QCOMPARE( authm->availableAuthMethodConfigs().size(), 3 );

// Password-protected export / import
QVERIFY( authm->exportAuthenticationConfigsToXml( mTempDir + QStringLiteral( "/configs.xml" ), idcfgmap.keys(), QStringLiteral( "1234" ) ) );
QVERIFY( authm->removeAllAuthenticationConfigs() );
QVERIFY( authm->importAuthenticationConfigsFromXml( mTempDir + QStringLiteral( "/configs.xml" ), QStringLiteral( "1234" ) ) );

QgsAuthMethodConfigsMap authmap( authm->availableAuthMethodConfigs() );
QCOMPARE( authmap.size(), 3 );

Expand Down

0 comments on commit f916c06

Please sign in to comment.