Merge pull request #5526 from boundlessgeo/ogr_authconfig_2

[auth][needs-docs] Authentication configuration support in OGR provider
This commit is contained in:
Alessandro Pasotti 2017-11-16 09:00:04 +01:00 committed by GitHub
commit 8dd70c1dab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 907 additions and 278 deletions

View File

@ -33,3 +33,4 @@ PyQgsServerAccessControl
# Need a local postgres installation
PyQgsAuthManagerPKIPostgresTest
PyQgsAuthManagerPasswordPostgresTest
PyQgsAuthManagerOgrPostgresTest

View File

@ -42,6 +42,7 @@ QgsAuthBasicMethod::QgsAuthBasicMethod()
<< QStringLiteral( "wfs" ) // convert to lowercase
<< QStringLiteral( "wcs" )
<< QStringLiteral( "wms" )
<< QStringLiteral( "ogr" )
<< QStringLiteral( "proxy" ) );
}
@ -85,7 +86,6 @@ bool QgsAuthBasicMethod::updateNetworkRequest( QNetworkRequest &request, const Q
bool QgsAuthBasicMethod::updateDataSourceUriItems( QStringList &connectionItems, const QString &authcfg,
const QString &dataprovider )
{
Q_UNUSED( dataprovider )
QgsAuthMethodConfig mconfig = getMethodConfig( authcfg );
if ( !mconfig.isValid() )
{
@ -102,29 +102,8 @@ bool QgsAuthBasicMethod::updateDataSourceUriItems( QStringList &connectionItems,
return false;
}
QString userparam = "user='" + escapeUserPass( username ) + '\'';
int userindx = connectionItems.indexOf( QRegExp( "^user='.*" ) );
if ( userindx != -1 )
{
connectionItems.replace( userindx, userparam );
}
else
{
connectionItems.append( userparam );
}
QString passparam = "password='" + escapeUserPass( password ) + '\'';
int passindx = connectionItems.indexOf( QRegExp( "^password='.*" ) );
if ( passindx != -1 )
{
connectionItems.replace( passindx, passparam );
}
else
{
connectionItems.append( passparam );
}
// add extra CAs
// SSL Extra CAs
QString caparam;
QList<QSslCertificate> cas;
cas = QgsApplication::authManager()->trustedCaCerts();
// save CAs to temp file
@ -134,18 +113,164 @@ bool QgsAuthBasicMethod::updateDataSourceUriItems( QStringList &connectionItems,
QgsAuthCertUtils::certsToPemText( cas ) );
if ( ! caFilePath.isEmpty() )
{
QString caparam = "sslrootcert='" + caFilePath + "'";
int sslcaindx = connectionItems.indexOf( QRegExp( "^sslrootcert='.*" ) );
if ( sslcaindx != -1 )
caparam = "sslrootcert='" + caFilePath + "'";
}
// Branch for OGR
if ( dataprovider == QStringLiteral( "ogr" ) )
{
if ( ! password.isEmpty() )
{
connectionItems.replace( sslcaindx, caparam );
QString fullUri( connectionItems.first() );
QString uri( fullUri );
// Handle sub-layers
if ( fullUri.contains( '|' ) )
{
uri = uri.left( uri.indexOf( '|' ) );
}
// At least username must be set... password can be empty
if ( ! username.isEmpty() )
{
// Inject credentials
if ( uri.startsWith( QStringLiteral( "PG:" ) ) )
{
bool chopped = false;
if ( uri.endsWith( '"' ) )
{
uri.chop( 1 );
chopped = true;
}
if ( !username.isEmpty() )
{
uri += QStringLiteral( " user='%1'" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( " password='%1'" ).arg( password );
}
// add extra CAs
if ( ! caparam.isEmpty() )
{
uri += ' ' + caparam;
}
if ( chopped )
uri += '"';
}
else if ( uri.startsWith( QStringLiteral( "SDE:" ) ) )
{
uri = uri.replace( QRegExp( ",$" ), QStringLiteral( ",%1,%2" ).arg( username, password ) );
}
else if ( uri.startsWith( QStringLiteral( "IDB" ) ) )
{
bool chopped = false;
if ( uri.endsWith( '"' ) )
{
uri.chop( 1 );
chopped = true;
}
uri += QStringLiteral( " user=%1" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( " pass=%1" ).arg( password );
if ( chopped )
uri += '"';
}
else if ( uri.startsWith( QStringLiteral( "@driver=ingres" ) ) )
{
uri += QStringLiteral( ",userid=%1" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( ",password=%1" ).arg( password );
}
else if ( uri.startsWith( QStringLiteral( "MySQL:" ) ) )
{
uri += QStringLiteral( ",user=%1" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( ",password=%1" ).arg( password );
}
else if ( uri.startsWith( QStringLiteral( "MSSQL:" ) ) )
{
uri += QStringLiteral( ";uid=%1" ).arg( username );
uri = uri.replace( QLatin1String( ";trusted_connection=yes" ), QString() );
if ( !password.isEmpty() )
uri += QStringLiteral( ";pwd=%1" ).arg( password );
}
else if ( uri.startsWith( QStringLiteral( "OCI:" ) ) )
{
// OCI:userid/password@database_instance:table,table
uri = uri.replace( QStringLiteral( "OCI:/" ), QStringLiteral( "OCI:%1/%2" ).arg( username, password ) );
}
else if ( uri.startsWith( QStringLiteral( "ODBC:" ) ) )
{
if ( password.isEmpty() )
{
uri = uri.replace( QRegExp( "^ODBC:@?" ), "ODBC:" + username + '@' );
}
else
{
uri = uri.replace( QRegExp( "^ODBC:@?" ), "ODBC:" + username + '/' + password + '@' );
}
}
else if ( uri.startsWith( QStringLiteral( "couchdb" ) )
|| uri.startsWith( QStringLiteral( "DODS" ) )
|| uri.startsWith( "http://" )
|| uri.startsWith( "https://" )
|| uri.startsWith( "ftp://" ) // not really sure that this is supported ...
)
{
uri = uri.replace( QStringLiteral( "://" ), QStringLiteral( "://%1:%2@" ).arg( username, password ) );
}
}
// Handle sub-layers
if ( fullUri.contains( '|' ) )
{
uri += '|' + fullUri.right( fullUri.length() - fullUri.lastIndexOf( '|' ) - 1 );
}
connectionItems.replace( 0, uri );
}
else
{
connectionItems.append( caparam );
QgsDebugMsg( QString( "Update URI items FAILED for authcfg: %1: password empty" ).arg( authcfg ) );
}
}
else // Not-ogr
{
QString userparam = "user='" + escapeUserPass( username ) + '\'';
int userindx = connectionItems.indexOf( QRegExp( "^user='.*" ) );
if ( userindx != -1 )
{
connectionItems.replace( userindx, userparam );
}
else
{
connectionItems.append( userparam );
}
QString passparam = "password='" + escapeUserPass( password ) + '\'';
int passindx = connectionItems.indexOf( QRegExp( "^password='.*" ) );
if ( passindx != -1 )
{
connectionItems.replace( passindx, passparam );
}
else
{
connectionItems.append( passparam );
}
// add extra CAs
if ( ! caparam.isEmpty() )
{
int sslcaindx = connectionItems.indexOf( QRegExp( "^sslrootcert='.*" ) );
if ( sslcaindx != -1 )
{
connectionItems.replace( sslcaindx, caparam );
}
else
{
connectionItems.append( caparam );
}
}
}
return true;
}

View File

@ -57,18 +57,25 @@ QgsNewOgrConnection::QgsNewOgrConnection( QWidget *parent, const QString &connTy
txtDatabase->setText( settings.value( key + "/database" ).toString() );
QString port = settings.value( key + "/port" ).toString();
txtPort->setText( port );
txtUsername->setText( settings.value( key + "/username" ).toString() );
if ( settings.value( key + "/save" ).toString() == QLatin1String( "true" ) )
if ( settings.value( key + "/store_username" ).toString() == QLatin1String( "true" ) )
{
txtPassword->setText( settings.value( key + "/password" ).toString() );
chkStorePassword->setChecked( true );
mAuthSettingsDatabase->setUsername( settings.value( key + "/username" ).toString() );
mAuthSettingsDatabase->setStoreUsernameChecked( true );
}
if ( settings.value( key + "/store_password" ).toString() == QLatin1String( "true" ) )
{
mAuthSettingsDatabase->setPassword( settings.value( key + "/password" ).toString() );
mAuthSettingsDatabase->setStorePasswordChecked( true );
}
mAuthSettingsDatabase->setConfigId( settings.value( key + "/configid" ).toString() );
cmbDatabaseTypes->setCurrentIndex( cmbDatabaseTypes->findText( connType ) );
txtName->setText( connName );
txtName->setEnabled( false );
cmbDatabaseTypes->setEnabled( false );
}
txtName->setValidator( new QRegExpValidator( QRegExp( "[^\\/]+" ), txtName ) );
mAuthSettingsDatabase->setDataprovider( QStringLiteral( "ogr" ) );
mAuthSettingsDatabase->showStoreCheckboxes( true );
}
QgsNewOgrConnection::~QgsNewOgrConnection()
@ -80,9 +87,14 @@ QgsNewOgrConnection::~QgsNewOgrConnection()
void QgsNewOgrConnection::testConnection()
{
QString uri;
uri = createDatabaseURI( cmbDatabaseTypes->currentText(), txtHost->text(),
txtDatabase->text(), txtPort->text(),
txtUsername->text(), txtPassword->text() );
uri = createDatabaseURI( cmbDatabaseTypes->currentText(),
txtHost->text(),
txtDatabase->text(),
txtPort->text(),
mAuthSettingsDatabase->configId(),
mAuthSettingsDatabase->username(),
mAuthSettingsDatabase->password(),
true );
QgsDebugMsg( "Connecting using uri = " + uri );
OGRRegisterAll();
OGRDataSourceH poDS;
@ -133,9 +145,11 @@ void QgsNewOgrConnection::accept()
settings.setValue( baseKey + "/host", txtHost->text() );
settings.setValue( baseKey + "/database", txtDatabase->text() );
settings.setValue( baseKey + "/port", txtPort->text() );
settings.setValue( baseKey + "/username", txtUsername->text() );
settings.setValue( baseKey + "/password", chkStorePassword->isChecked() ? txtPassword->text() : QLatin1String( "" ) );
settings.setValue( baseKey + "/save", chkStorePassword->isChecked() ? "true" : "false" );
settings.setValue( baseKey + "/username", mAuthSettingsDatabase->storeUsernameIsChecked() ? mAuthSettingsDatabase->username() : QLatin1String( "" ) );
settings.setValue( baseKey + "/password", mAuthSettingsDatabase->storePasswordIsChecked() ? mAuthSettingsDatabase->password() : QLatin1String( "" ) );
settings.setValue( baseKey + "/store_username", mAuthSettingsDatabase->storeUsernameIsChecked() ? "true" : "false" );
settings.setValue( baseKey + "/store_password", mAuthSettingsDatabase->storePasswordIsChecked() ? "true" : "false" );
settings.setValue( baseKey + "/configid", mAuthSettingsDatabase->configId() );
QDialog::accept();
}

View File

@ -18,12 +18,24 @@
#include "qgsogrhelperfunctions.h"
#include "qgslogger.h"
#include "qgsapplication.h"
#include "qgsauthmanager.h"
#include <QRegExp>
QString createDatabaseURI( const QString &connectionType, const QString &host, const QString &database, QString port, const QString &user, const QString &password )
QString createDatabaseURI( const QString &connectionType, const QString &host, const QString &database, QString port, const QString &configId, QString username, QString password, bool expandAuthConfig )
{
QString uri;
// If an auth configuration is set, override username and password
// Note that only Basic auth (username/password) is for now supported for OGR connections
if ( ! configId.isEmpty() )
{
// Blank credentials: we are using authcfg!
username = QString();
password = QString();
// append authcfg is at the end, because we want to append the authcfg as last argument
}
//todo:add default ports for all kind of databases
if ( connectionType == QLatin1String( "ESRI Personal GeoDatabase" ) )
{
@ -34,7 +46,7 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
if ( port.isEmpty() )
port = QStringLiteral( "5151" );
uri = "SDE:" + host + ",PORT:" + port + ',' + database + ',' + user + ',' + password;
uri = "SDE:" + host + ",PORT:" + port + ',' + database + ',' + username + ',' + password;
}
else if ( connectionType == QLatin1String( "Informix DataBlade" ) )
{
@ -44,9 +56,9 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
if ( !host.isEmpty() )
uri += QStringLiteral( " server=%1" ).arg( host );
if ( !user.isEmpty() )
if ( !username.isEmpty() )
{
uri += QStringLiteral( " user=%1" ).arg( user );
uri += QStringLiteral( " user=%1" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( " pass=%1" ).arg( password );
@ -56,9 +68,9 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
{
//not tested
uri = "@driver=ingres,dbname=" + database;
if ( !user.isEmpty() )
if ( !username.isEmpty() )
{
uri += QStringLiteral( ",userid=%1" ).arg( user );
uri += QStringLiteral( ",userid=%1" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( ",password=%1" ).arg( password );
@ -76,9 +88,9 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
uri += QStringLiteral( ",port=%1" ).arg( port );
}
if ( !user.isEmpty() )
if ( !username.isEmpty() )
{
uri += QStringLiteral( ",user=%1" ).arg( user );
uri += QStringLiteral( ",user=%1" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( ",password=%1" ).arg( password );
@ -96,9 +108,9 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
uri += QStringLiteral( ",%1" ).arg( port );
}
if ( !user.isEmpty() )
if ( !username.isEmpty() )
{
uri += QStringLiteral( ";uid=%1" ).arg( user );
uri += QStringLiteral( ";uid=%1" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( ";pwd=%1" ).arg( password );
@ -111,10 +123,10 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
}
else if ( connectionType == QLatin1String( "Oracle Spatial" ) )
{
uri = "OCI:" + user;
uri = "OCI:" + username;
if ( ( !user.isEmpty() && !password.isEmpty() ) ||
( user.isEmpty() && password.isEmpty() ) )
if ( ( !username.isEmpty() && !password.isEmpty() ) ||
( username.isEmpty() && password.isEmpty() ) )
{
uri += '/';
if ( !password.isEmpty() )
@ -142,15 +154,15 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
}
else if ( connectionType == QLatin1String( "ODBC" ) )
{
if ( !user.isEmpty() )
if ( !username.isEmpty() )
{
if ( password.isEmpty() )
{
uri = "ODBC:" + user + '@' + database;
uri = "ODBC:" + username + '@' + database;
}
else
{
uri = "ODBC:" + user + '/' + password + '@' + database;
uri = "ODBC:" + username + '/' + password + '@' + database;
}
}
@ -174,9 +186,9 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
uri += QStringLiteral( " port='%1'" ).arg( port );
}
if ( !user.isEmpty() )
if ( !username.isEmpty() )
{
uri += QStringLiteral( " user='%1'" ).arg( user );
uri += QStringLiteral( " user='%1'" ).arg( username );
if ( !password.isEmpty() )
uri += QStringLiteral( " password='%1'" ).arg( password );
@ -184,13 +196,29 @@ QString createDatabaseURI( const QString &connectionType, const QString &host, c
uri += ' ';
}
// Append authentication configuration to the URI
if ( !( configId.isEmpty() ) )
{
if ( ! expandAuthConfig )
{
uri += QStringLiteral( " authcfg='%1'" ).arg( configId );
}
else
{
QStringList connectionItems;
connectionItems << uri;
if ( QgsApplication::authManager()->updateDataSourceUriItems( connectionItems, configId, QStringLiteral( "ogr" ) ) )
{
uri = connectionItems.join( QString() );
}
}
}
QgsDebugMsg( "Connection type is=" + connectionType + " and uri=" + uri );
return uri;
}
QString createProtocolURI( const QString &type, const QString &url )
QString createProtocolURI( const QString &type, const QString &url, const QString &configId, const QString &username, const QString &password, bool expandAuthConfig )
{
QString uri;
if ( type == QLatin1String( "GeoJSON" ) )
@ -206,5 +234,26 @@ QString createProtocolURI( const QString &type, const QString &url )
uri = QStringLiteral( "DODS:%1" ).arg( url );
}
QgsDebugMsg( "Connection type is=" + type + " and uri=" + uri );
// Update URI with authentication information
if ( ! configId.isEmpty() )
{
if ( expandAuthConfig )
{
QStringList connectionItems;
connectionItems << uri;
if ( QgsApplication::authManager()->updateDataSourceUriItems( connectionItems, configId, QStringLiteral( "ogr" ) ) )
{
uri = connectionItems.join( QString() );
}
}
else
{
uri += QStringLiteral( " authcfg='%1'" ).arg( configId );
}
}
else if ( !( username.isEmpty() || password.isEmpty( ) ) )
{
uri.replace( QStringLiteral( "://" ), QStringLiteral( "://%1:%2@" ).arg( username, password ) );
}
return uri;
}

View File

@ -26,11 +26,11 @@
* \brief Create database uri from connection parameters
* \note not available in python bindings
*/
QString GUI_EXPORT createDatabaseURI( const QString &connectionType, const QString &host, const QString &database, QString port, const QString &user, const QString &password );
QString GUI_EXPORT createDatabaseURI( const QString &connectionType, const QString &host, const QString &database, QString port, const QString &configId, QString username, QString password, bool expandAuthConfig = false );
/**
* CreateProtocolURI
* \brief Create protocol uri from connection parameters
* \note not available in python bindings
*/
QString GUI_EXPORT createProtocolURI( const QString &type, const QString &url );
QString GUI_EXPORT createProtocolURI( const QString &type, const QString &url, const QString &configId, const QString &username, const QString &password, bool expandAuthConfig = false );

View File

@ -422,7 +422,7 @@ bool QgsOgrFeatureIterator::readFeature( gdal::ogr_feature_unique_ptr fet, QgsFe
QgsOgrFeatureSource::QgsOgrFeatureSource( const QgsOgrProvider *p )
: mDataSource( p->dataSourceUri() )
: mDataSource( p->dataSourceUri( true ) )
, mLayerName( p->layerName() )
, mLayerIndex( p->layerIndex() )
, mSubsetString( p->mSubsetString )

View File

@ -469,21 +469,33 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri )
setNativeTypes( nativeTypes );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
}
QgsOgrProvider::~QgsOgrProvider()
{
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
// We must also make sure to flush unusef cached connections so that
// the file can be removed (#15137)
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
// Do that as last step for final cleanup that might be prevented by
// still opened datasets.
close();
}
QString QgsOgrProvider::dataSourceUri( bool expandAuthConfig ) const
{
if ( expandAuthConfig && QgsDataProvider::dataSourceUri( ).contains( QLatin1String( "authcfg" ) ) )
{
return QgsOgrProviderUtils::expandAuthConfig( QgsDataProvider::dataSourceUri( ) );
}
else
{
return QgsDataProvider::dataSourceUri( );
}
}
QgsAbstractFeatureSource *QgsOgrProvider::featureSource() const
{
return new QgsOgrFeatureSource( this );
@ -549,9 +561,9 @@ bool QgsOgrProvider::setSubsetString( const QString &theSQL, bool updateFeatureC
if ( uri != dataSourceUri() )
{
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
setDataSourceUri( uri );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
}
mOgrLayer->ResetReading();
@ -901,7 +913,7 @@ OGRwkbGeometryType QgsOgrProvider::getOgrGeomType( OGRLayerH ogrLayer )
void QgsOgrProvider::loadFields()
{
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
//the attribute fields need to be read again when the encoding changes
mAttributeFields.clear();
mDefaultValues.clear();
@ -1482,7 +1494,7 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
{
// adding attributes in mapinfo requires to be able to delete the .dat file
// so drop any cached connections.
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
}
bool returnvalue = true;
@ -1825,7 +1837,7 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
{
pushError( tr( "OGR error syncing to disk: %1" ).arg( CPLGetLastErrorMsg() ) );
}
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
return true;
}
@ -1901,7 +1913,7 @@ bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
commitTransaction();
}
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
return syncToDisc();
}
@ -3294,7 +3306,7 @@ QByteArray QgsOgrProvider::quotedIdentifier( const QByteArray &field ) const
void QgsOgrProvider::forceReload()
{
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
}
QString QgsOgrProviderUtils::connectionPoolId( const QString &dataSourceURI )
@ -3576,7 +3588,7 @@ QString QgsOgrProviderUtils::quotedValue( const QVariant &value )
bool QgsOgrProvider::syncToDisc()
{
//for shapefiles, remove spatial index files and create a new index
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
bool shapeIndex = false;
if ( mGDALDriverName == QLatin1String( "ESRI Shapefile" ) )
{
@ -3591,7 +3603,7 @@ bool QgsOgrProvider::syncToDisc()
{
shapeIndex = true;
close();
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QFile::remove( sbnIndexFile );
open( OpenModeSameAsCurrent );
if ( !mValid )
@ -3615,7 +3627,7 @@ bool QgsOgrProvider::syncToDisc()
}
#endif
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
if ( shapeIndex )
{
return createSpatialIndex();
@ -3677,7 +3689,7 @@ void QgsOgrProvider::recalculateFeatureCount()
mOgrLayer->SetSpatialFilter( filter );
}
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri() ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
}
bool QgsOgrProvider::doesStrictFeatureTypeCheck() const
@ -4091,7 +4103,7 @@ static GDALDatasetH OpenHelper( const QString &dsName,
option.toUtf8().constData() );
}
GDALDatasetH hDS = QgsOgrProviderUtils::GDALOpenWrapper(
dsName.toUtf8().constData(), updateMode, papszOpenOptions, nullptr );
QgsOgrProviderUtils::expandAuthConfig( dsName ).toUtf8().constData(), updateMode, papszOpenOptions, nullptr );
CSLDestroy( papszOpenOptions );
return hDS;
}
@ -4241,6 +4253,7 @@ QgsOgrLayer *QgsOgrProviderUtils::getLayer( const QString &dsName,
QString &errCause )
{
QMutexLocker locker( &globalMutex );
for ( auto iter = mapSharedDS.begin(); iter != mapSharedDS.end(); ++iter )
{
if ( iter.key().dsName == dsName )
@ -4290,6 +4303,26 @@ static QDateTime getLastModified( const QString &dsName )
return QFileInfo( dsName ).lastModified();
}
QString QgsOgrProviderUtils::expandAuthConfig( const QString &dsName )
{
QString uri( dsName );
// Check for authcfg
QRegularExpression authcfgRe( " authcfg='([^']+)'" );
QRegularExpressionMatch match;
if ( uri.contains( authcfgRe, &match ) )
{
uri = uri.replace( match.captured( 0 ), QString() );
QString configId( match.captured( 1 ) );
QStringList connectionItems;
connectionItems << uri;
if ( QgsApplication::authManager()->updateDataSourceUriItems( connectionItems, configId, QStringLiteral( "ogr" ) ) )
{
uri = connectionItems.first( );
}
}
return uri;
}
// Must be called under the globalMutex
bool QgsOgrProviderUtils::canUseOpenedDatasets( const QString &dsName )
{

View File

@ -64,6 +64,18 @@ class QgsOgrProvider : public QgsVectorDataProvider
virtual ~QgsOgrProvider();
/**
* Get the data source specification. This may be a path or database
* connection string
* \param expandAuthConfig Whether to expand any assigned authentication configuration
* \returns data source specification
* \note The default authentication configuration expansion is FALSE. This keeps credentials
* out of layer data source URIs and project files. Expansion should be specifically done
* only when needed within a provider
*/
QString dataSourceUri( bool expandAuthConfig = false ) const override;
virtual QgsAbstractFeatureSource *featureSource() const override;
virtual QgsCoordinateReferenceSystem crs() const override;
@ -325,6 +337,10 @@ class QgsOgrProviderUtils
static bool canUseOpenedDatasets( const QString &dsName );
public:
//! Inject credentials into the dsName (if any)
static QString expandAuthConfig( const QString &dsName );
static void setRelevantFields( OGRLayerH ogrLayer, int fieldCount, bool fetchGeometry, const QgsAttributeList &fetchAttributes, bool firstAttrIsFid );
static OGRLayerH setSubsetString( OGRLayerH layer, GDALDatasetH ds, QTextCodec *encoding, const QString &subsetString, bool &origFidAdded );
static QByteArray quotedIdentifier( QByteArray field, const QString &driverName );

View File

@ -123,6 +123,8 @@ QgsOgrSourceSelect::QgsOgrSourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs
if ( radioSrcProtocol->isChecked() )
emit enableButtons( !text.isEmpty() );
} );
// Set filter for ogr compatible auth methods
mAuthSettingsProtocol->setDataprovider( QStringLiteral( "ogr" ) );
}
QgsOgrSourceSelect::~QgsOgrSourceSelect()
@ -179,6 +181,7 @@ void QgsOgrSourceSelect::deleteConnection()
settings.remove( key + "/password" );
settings.remove( key + "/port" );
settings.remove( key + "/save" );
settings.remove( key + "/autchcfg" );
settings.remove( key );
cmbConnections->removeItem( cmbConnections->currentIndex() ); // populateConnectionList();
setConnectionListPosition();
@ -285,9 +288,10 @@ void QgsOgrSourceSelect::addButtonClicked()
QString port = settings.value( baseKey + "/port" ).toString();
QString user = settings.value( baseKey + "/username" ).toString();
QString pass = settings.value( baseKey + "/password" ).toString();
QString configid = settings.value( baseKey + "/configid" ).toString();
bool makeConnection = false;
if ( pass.isEmpty() )
if ( pass.isEmpty() && configid.isEmpty( ) )
{
if ( cmbDatabaseTypes->currentText() == QLatin1String( "MSSQL" ) )
makeConnection = true;
@ -299,13 +303,14 @@ void QgsOgrSourceSelect::addButtonClicked()
&makeConnection );
}
if ( makeConnection || !pass.isEmpty() )
if ( makeConnection || !( pass.isEmpty() && configid.isEmpty( ) ) )
{
mDataSources << createDatabaseURI(
cmbDatabaseTypes->currentText(),
host,
database,
port,
configid,
user,
pass
);
@ -321,7 +326,11 @@ void QgsOgrSourceSelect::addButtonClicked()
return;
}
mDataSources << createProtocolURI( cmbProtocolTypes->currentText(), protocolURI->text() );
mDataSources << createProtocolURI( cmbProtocolTypes->currentText(),
protocolURI->text(),
mAuthSettingsProtocol->configId(),
mAuthSettingsProtocol->username(),
mAuthSettingsProtocol->password() );
}
else if ( radioSrcFile->isChecked() )
{

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>404</width>
<height>348</height>
<height>386</height>
</rect>
</property>
<property name="sizePolicy">
@ -25,7 +25,7 @@
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout">
<layout class="QGridLayout" name="grLayout1">
<property name="leftMargin">
<number>9</number>
</property>
@ -41,42 +41,77 @@
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="GroupBox1">
<property name="title">
<string>Connection Information</string>
</property>
<layout class="QGridLayout" name="gridLayout_1">
<layout class="QGridLayout" name="gridLayout">
<item row="8" column="0" colspan="4">
<widget class="QPushButton" name="btnConnect">
<property name="text">
<string>&amp;Test Connection</string>
</property>
</widget>
</item>
<item row="5" column="0" rowspan="3" colspan="4">
<widget class="QGroupBox" name="mAuthGroupBox">
<property name="title">
<string>Authentication</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QgsAuthSettingsWidget" name="mAuthSettingsDatabase" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Type</string>
<string>&amp;Type</string>
</property>
<property name="buddy">
<cstring>cmbDatabaseTypes</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="cmbDatabaseTypes"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="TextLabel1_2">
<property name="text">
<string>Name</string>
<string>&amp;Name</string>
</property>
<property name="buddy">
<cstring>txtName</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="txtName">
<property name="toolTip">
<string>Name of the new connection</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="TextLabel1">
<property name="text">
@ -87,22 +122,16 @@
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="txtHost"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="TextLabel2">
<property name="text">
<string>Database</string>
<string>&amp;Database</string>
</property>
<property name="buddy">
<cstring>txtDatabase</cstring>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QLineEdit" name="txtDatabase"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="TextLabel2_2">
<property name="text">
@ -113,87 +142,48 @@
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<item row="1" column="1" colspan="3">
<widget class="QLineEdit" name="txtName">
<property name="toolTip">
<string>Name of the new connection</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="cmbDatabaseTypes"/>
</item>
<item row="2" column="1" colspan="3">
<widget class="QLineEdit" name="txtHost"/>
</item>
<item row="3" column="1" colspan="3">
<widget class="QLineEdit" name="txtDatabase"/>
</item>
<item row="4" column="1" colspan="3">
<widget class="QLineEdit" name="txtPort">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="TextLabel3">
<property name="text">
<string>Username</string>
</property>
<property name="buddy">
<cstring>txtUsername</cstring>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QLineEdit" name="txtUsername"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="TextLabel3_2">
<property name="text">
<string>Password</string>
</property>
<property name="buddy">
<cstring>txtPassword</cstring>
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<widget class="QgsPasswordLineEdit" name="txtPassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="chkStorePassword">
<property name="text">
<string>Save Password</string>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QPushButton" name="btnConnect">
<property name="text">
<string>&amp;Test Connect</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>QgsPasswordLineEdit</class>
<extends>QLineEdit</extends>
<header>qgspasswordlineedit.h</header>
<class>QgsAuthSettingsWidget</class>
<extends>QWidget</extends>
<header>auth/qgsauthsettingswidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>cmbDatabaseTypes</tabstop>
<tabstop>txtName</tabstop>
<tabstop>txtHost</tabstop>
<tabstop>txtDatabase</tabstop>
<tabstop>txtPort</tabstop>
<tabstop>txtUsername</tabstop>
<tabstop>txtPassword</tabstop>
<tabstop>chkStorePassword</tabstop>
<tabstop>btnConnect</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>

View File

@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>450</width>
<height>575</height>
<height>658</height>
</rect>
</property>
<property name="sizePolicy">
@ -26,15 +26,76 @@
<iconset>
<normaloff>.</normaloff>.</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="5" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Help</set>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QGroupBox" name="srcGroupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Source type</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="radioSrcFile">
<property name="text">
<string>F&amp;ile</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSrcDirectory">
<property name="text">
<string>&amp;Directory</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSrcDatabase">
<property name="text">
<string>Da&amp;tabase</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSrcProtocol">
<property name="text">
<string>Protoco&amp;l</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Encoding</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cmbEncodings">
<property name="minimumSize">
<size>
<width>341</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<item>
<widget class="QGroupBox" name="protocolGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -52,19 +113,19 @@
<string>Protocol</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1" rowspan="2">
<widget class="QComboBox" name="cmbProtocolTypes"/>
</item>
<item row="1" column="0" rowspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>URI</string>
<string>&amp;URI</string>
</property>
<property name="buddy">
<cstring>protocolURI</cstring>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<widget class="QComboBox" name="cmbProtocolTypes"/>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="protocolURI"/>
</item>
@ -75,10 +136,41 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QGroupBox" name="mAuthGroupBox">
<property name="title">
<string>Authentication</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QgsAuthSettingsWidget" name="mAuthSettingsProtocol" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<item>
<widget class="QGroupBox" name="fileGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -124,12 +216,12 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QgsFileWidget" name="mFileWidget"/>
<widget class="QgsFileWidget" name="mFileWidget" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<item>
<widget class="QGroupBox" name="dbGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -206,75 +298,7 @@
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="srcGroupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Source type</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="radioSrcFile">
<property name="text">
<string>File</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSrcDirectory">
<property name="text">
<string>Directory</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSrcDatabase">
<property name="text">
<string>Database</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSrcProtocol">
<property name="text">
<string>Protocol</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Encoding</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cmbEncodings">
<property name="minimumSize">
<size>
<width>341</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="4" column="0">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -287,6 +311,13 @@
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Help</set>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
@ -296,6 +327,12 @@
<extends>QWidget</extends>
<header>qgsfilewidget.h</header>
</customwidget>
<customwidget>
<class>QgsAuthSettingsWidget</class>
<extends>QWidget</extends>
<header>auth/qgsauthsettingswidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>radioSrcFile</tabstop>
@ -316,38 +353,6 @@
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QgsOgrSourceSelectDialogBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>451</x>
<y>699</y>
</hint>
<hint type="destinationlabel">
<x>481</x>
<y>297</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QgsOgrSourceSelectDialogBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>392</x>
<y>699</y>
</hint>
<hint type="destinationlabel">
<x>281</x>
<y>339</y>
</hint>
</hints>
</connection>
<connection>
<sender>radioSrcDatabase</sender>
<signal>toggled(bool)</signal>

View File

@ -196,6 +196,7 @@ ADD_PYTHON_TEST(PyQgsSourceSelectProvider test_qgssourceselectprovider.py)
ADD_PYTHON_TEST(PyQgsAuthManagerProxy test_authmanager_proxy.py)
ADD_PYTHON_TEST(PyQgsAuthSettingsWidget test_authsettingswidget.py)
ADD_PYTHON_TEST(PyQgsAuxiliaryStorage test_qgsauxiliarystorage.py)
ADD_PYTHON_TEST(PyQgsAuthManagerOgr test_authmanager_ogr.py)
IF (NOT WIN32)
ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py)
@ -216,7 +217,8 @@ IF (ENABLE_PGTEST)
ADD_PYTHON_TEST(PyQgsVectorLayerTools test_qgsvectorlayertools.py)
ADD_PYTHON_TEST(PyQgsAuthManagerPKIPostgresTest test_authmanager_pki_postgres.py)
ADD_PYTHON_TEST(PyQgsAuthManagerPasswordPostgresTest test_authmanager_password_postgres.py)
ENDIF (ENABLE_PGTEST)
ADD_PYTHON_TEST(PyQgsAuthManagerOgrPostgresTest test_authmanager_ogr_postgres.py)
ENDIF (ENABLE_PGTEST)
IF (ENABLE_MSSQLTEST)
ADD_PYTHON_TEST(PyQgsMssqlProvider test_provider_mssql.py)

View File

@ -90,6 +90,16 @@ if os.environ.get('QGIS_SERVER_HTTP_BASIC_AUTH') is not None:
class HTTPBasicFilter(QgsServerFilter):
def requestReady(self):
handler = self.serverInterface().requestHandler()
auth = self.serverInterface().requestHandler().requestHeader('HTTP_AUTHORIZATION')
if auth:
username, password = base64.b64decode(auth[6:]).split(b':')
if (username.decode('utf-8') == os.environ.get('QGIS_SERVER_USERNAME', 'username') and
password.decode('utf-8') == os.environ.get('QGIS_SERVER_PASSWORD', 'password')):
return
handler.setParameter('SERVICE', 'ACCESS_DENIED')
def responseComplete(self):
handler = self.serverInterface().requestHandler()
auth = self.serverInterface().requestHandler().requestHeader('HTTP_AUTHORIZATION')

View File

@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
"""
Tests for auth manager Basic Auth OGR connection credentials injection
From build dir, run: ctest -R PyQgsAuthManagerOgr -V
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
from qgis.core import (
QgsApplication,
QgsAuthManager,
QgsAuthMethodConfig,
QgsDataSourceUri,
QgsProviderRegistry,
)
from qgis.testing import (
start_app,
unittest,
)
__author__ = 'Alessandro Pasotti'
__date__ = '14/11/2017'
__copyright__ = 'Copyright 2017, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
qgis_app = start_app()
# Note: value is checked with "in" because some drivers may need additional arguments,
# like temporary paths with rootcerts for PG
TEST_URIS = {
"http://mysite.com/geojson authcfg='%s'": "http://username:password@mysite.com/geojson",
"PG:\"dbname='databasename' host='addr' port='5432' authcfg='%s'\"": "PG:\"dbname='databasename' host='addr' port='5432' user='username' password='password'",
'SDE:127.0.0.1,12345,dbname, authcfg=\'%s\'': 'SDE:127.0.0.1,12345,dbname,username,password',
'IDB:"server=demo_on user=informix dbname=frames authcfg=\'%s\'"': 'IDB:"server=demo_on user=informix dbname=frames user=username pass=password"',
'@driver=ingres,dbname=test,tables=usa/canada authcfg=\'%s\'': '@driver=ingres,dbname=test,tables=usa/canada,userid=username,password=password',
'MySQL:westholland,port=3306,tables=bedrijven authcfg=\'%s\'': 'MySQL:westholland,port=3306,tables=bedrijven,user=username,password=password',
'MSSQL:server=.\MSSQLSERVER2008;database=dbname;trusted_connection=yes authcfg=\'%s\'': 'MSSQL:server=.\MSSQLSERVER2008;database=dbname;uid=username;pwd=password',
'OCI:/@database_instance:table,table authcfg=\'%s\'': 'OCI:username/password@database_instance:table,table',
'ODBC:database_instance authcfg=\'%s\'': 'ODBC:username/password@database_instance',
'couchdb://myconnection authcfg=\'%s\'': 'couchdb://username:password@myconnection',
'http://www.myconnection.com/geojson authcfg=\'%s\'': 'http://username:password@www.myconnection.com/geojson',
'https://www.myconnection.com/geojson authcfg=\'%s\'': 'https://username:password@www.myconnection.com/geojson',
'ftp://www.myconnection.com/geojson authcfg=\'%s\'': 'ftp://username:password@www.myconnection.com/geojson',
'DODS://www.myconnection.com/geojson authcfg=\'%s\'': 'DODS://username:password@www.myconnection.com/geojson',
}
class TestAuthManager(unittest.TestCase):
@classmethod
def setUpAuth(cls):
"""Run before all tests and set up authentication"""
authm = QgsApplication.authManager()
assert (authm.setMasterPassword('masterpassword', True))
# Client side
cls.auth_config = QgsAuthMethodConfig("Basic")
cls.auth_config.setConfig('username', cls.username)
cls.auth_config.setConfig('password', cls.password)
cls.auth_config.setName('test_basic_auth_config')
assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
assert cls.auth_config.isValid()
cls.authcfg = cls.auth_config.id()
@classmethod
def setUpClass(cls):
"""Run before all tests:
Creates an auth configuration"""
cls.username = 'username'
cls.password = 'password'
cls.dbname = 'test_basic'
cls.hostname = 'localhost'
cls.setUpAuth()
@classmethod
def tearDownClass(cls):
"""Run after all tests"""
pass
def setUp(self):
"""Run before each test."""
pass
def tearDown(self):
"""Run after each test."""
pass
def testConnections(self):
"""
Test credentials injection
"""
pr = QgsProviderRegistry.instance().createProvider('ogr', '')
for uri, expanded in TEST_URIS.items():
pr.setDataSourceUri(uri % self.authcfg)
self.assertTrue(expanded in pr.dataSourceUri(True), "%s != %s" % (expanded, pr.dataSourceUri(True)))
# Test sublayers
for uri, expanded in TEST_URIS.items():
pr.setDataSourceUri((uri + '|sublayer1') % self.authcfg)
self.assertEqual(pr.dataSourceUri(True).split('|')[1], "sublayer1", pr.dataSourceUri(True))
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,246 @@
# -*- coding: utf-8 -*-
"""
Tests for auth manager Basic Auth access to postgres.
This is an integration test for QGIS Desktop Auth Manager postgres provider that
checks if QGIS can use a stored auth manager auth configuration to access
a username/password protected postgres.
Configuration from the environment:
* QGIS_POSTGRES_SERVER_PORT (default: 55432)
* QGIS_POSTGRES_EXECUTABLE_PATH (default: /usr/lib/postgresql/9.4/bin)
From build dir, run: ctest -R PyQgsAuthManagerOgrPostgresTest -V
or, if your PostgreSQL path differs from the default:
QGIS_POSTGRES_EXECUTABLE_PATH=/usr/lib/postgresql/<your_version_goes_here>/bin \
ctest -R PyQgsAuthManagerOgrPostgresTest -V
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
import os
import time
import signal
import stat
import subprocess
import tempfile
from shutil import rmtree
from utilities import unitTestDataPath
from qgis.core import (
QgsApplication,
QgsAuthManager,
QgsAuthMethodConfig,
QgsVectorLayer,
QgsDataSourceUri,
QgsWkbTypes,
)
from qgis.PyQt.QtNetwork import QSslCertificate
from qgis.testing import (
start_app,
unittest,
)
__author__ = 'Alessandro Pasotti'
__date__ = '03/11/2017'
__copyright__ = 'Copyright 2017, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
QGIS_POSTGRES_SERVER_PORT = os.environ.get('QGIS_POSTGRES_SERVER_PORT', '55432')
QGIS_POSTGRES_EXECUTABLE_PATH = os.environ.get('QGIS_POSTGRES_EXECUTABLE_PATH', '/usr/lib/postgresql/9.4/bin')
assert os.path.exists(QGIS_POSTGRES_EXECUTABLE_PATH)
QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
# Postgres test path
QGIS_PG_TEST_PATH = tempfile.mkdtemp()
os.environ['QGIS_AUTH_DB_DIR_PATH'] = QGIS_AUTH_DB_DIR_PATH
qgis_app = start_app()
QGIS_POSTGRES_CONF_TEMPLATE = """
hba_file = '%(tempfolder)s/pg_hba.conf'
listen_addresses = '*'
port = %(port)s
max_connections = 100
unix_socket_directories = '%(tempfolder)s'
ssl = true
ssl_ciphers = 'DEFAULT:!LOW:!EXP:!MD5:@STRENGTH' # allowed SSL ciphers
ssl_cert_file = '%(server_cert)s'
ssl_key_file = '%(server_key)s'
ssl_ca_file = '%(sslrootcert_path)s'
password_encryption = on
"""
QGIS_POSTGRES_HBA_TEMPLATE = """
hostssl all all 0.0.0.0/0 md5
hostssl all all ::1/0 md5
host all all 127.0.0.1/32 trust
host all all ::1/32 trust
host all all 0.0.0.0/0 trust
"""
class TestAuthManager(unittest.TestCase):
@classmethod
def setUpAuth(cls):
"""Run before all tests and set up authentication"""
authm = QgsApplication.authManager()
assert (authm.setMasterPassword('masterpassword', True))
cls.pg_conf = os.path.join(cls.tempfolder, 'postgresql.conf')
cls.pg_hba = os.path.join(cls.tempfolder, 'pg_hba.conf')
# Client side
cls.sslrootcert_path = os.path.join(cls.certsdata_path, 'chains_subissuer-issuer-root_issuer2-root2.pem')
assert os.path.isfile(cls.sslrootcert_path)
os.chmod(cls.sslrootcert_path, stat.S_IRUSR)
cls.auth_config = QgsAuthMethodConfig("Basic")
cls.auth_config.setConfig('username', cls.username)
cls.auth_config.setConfig('password', cls.password)
cls.auth_config.setName('test_basic_auth_config')
cls.sslrootcert = QSslCertificate.fromPath(cls.sslrootcert_path)
assert cls.sslrootcert is not None
authm.storeCertAuthorities(cls.sslrootcert)
authm.rebuildCaCertsCache()
authm.rebuildTrustedCaCertsCache()
authm.rebuildCertTrustCache()
assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
assert cls.auth_config.isValid()
cls.authcfg = cls.auth_config.id()
# Server side
cls.server_cert = os.path.join(cls.certsdata_path, 'localhost_ssl_cert.pem')
cls.server_key = os.path.join(cls.certsdata_path, 'localhost_ssl_key.pem')
cls.server_rootcert = cls.sslrootcert_path
os.chmod(cls.server_cert, stat.S_IRUSR)
os.chmod(cls.server_key, stat.S_IRUSR)
os.chmod(cls.server_rootcert, stat.S_IRUSR)
# Place conf in the data folder
with open(cls.pg_conf, 'w+') as f:
f.write(QGIS_POSTGRES_CONF_TEMPLATE % {
'port': cls.port,
'tempfolder': cls.tempfolder,
'server_cert': cls.server_cert,
'server_key': cls.server_key,
'sslrootcert_path': cls.sslrootcert_path,
})
with open(cls.pg_hba, 'w+') as f:
f.write(QGIS_POSTGRES_HBA_TEMPLATE)
@classmethod
def setUpClass(cls):
"""Run before all tests:
Creates an auth configuration"""
cls.port = QGIS_POSTGRES_SERVER_PORT
cls.username = 'username'
cls.password = 'password'
cls.dbname = 'test_basic'
cls.tempfolder = QGIS_PG_TEST_PATH
cls.certsdata_path = os.path.join(unitTestDataPath('auth_system'), 'certs_keys')
cls.hostname = 'localhost'
cls.data_path = os.path.join(cls.tempfolder, 'data')
os.mkdir(cls.data_path)
cls.setUpAuth()
subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'initdb'), '-D', cls.data_path])
# Disable SSL verification for setup operations
env = dict(os.environ)
env['PGSSLMODE'] = 'disable'
cls.server = subprocess.Popen([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'postgres'), '-D',
cls.data_path, '-c',
"config_file=%s" % cls.pg_conf],
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# Wait max 10 secs for the server to start
end = time.time() + 10
while True:
line = cls.server.stderr.readline()
print(line)
if line.find(b"database system is ready to accept") != -1:
break
if time.time() > end:
raise Exception("Timeout connecting to PostgreSQL")
# Create a DB
subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'createdb'), '-h', 'localhost', '-p', cls.port, 'test_basic'], env=env)
# Inject test SQL from test path
test_sql = os.path.join(unitTestDataPath('provider'), 'testdata_pg.sql')
subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'psql'), '-h', 'localhost', '-p', cls.port, '-f', test_sql, cls.dbname], env=env)
# Create a role
subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'psql'), '-h', 'localhost', '-p', cls.port, '-c', 'CREATE ROLE "%s" WITH SUPERUSER LOGIN PASSWORD \'%s\'' % (cls.username, cls.password), cls.dbname], env=env)
@classmethod
def tearDownClass(cls):
"""Run after all tests"""
cls.server.terminate()
os.kill(cls.server.pid, signal.SIGABRT)
del cls.server
time.sleep(2)
rmtree(QGIS_AUTH_DB_DIR_PATH)
rmtree(cls.tempfolder)
def setUp(self):
"""Run before each test."""
pass
def tearDown(self):
"""Run after each test."""
pass
@classmethod
def _getPostGISLayer(cls, type_name, layer_name=None, authcfg=''):
"""
PG layer factory
"""
if layer_name is None:
layer_name = 'pg_' + type_name
# Warning: OGR needs the schema if it's not the default, so qgis_test.someData
connstring = "PG:dbname='%(dbname)s' host='%(hostname)s' port='%(port)s' sslmode='verify-full' sslrootcert='%(sslrootcert)s'%(authcfg)s|layername=qgis_test.someData" % (
{
'dbname': cls.dbname,
'hostname': cls.hostname,
'port': cls.port,
'authcfg': ' authcfg=\'%s\'' % authcfg if authcfg else '',
'sslrootcert': cls.sslrootcert_path,
}
)
layer = QgsVectorLayer(connstring, layer_name, 'ogr')
return layer
def testValidAuthAccess(self):
"""
Access the protected layer with valid credentials
"""
pg_layer = self._getPostGISLayer('testlayer_èé', authcfg=self.auth_config.id())
self.assertTrue(pg_layer.isValid())
def testInvalidAuthAccess(self):
"""
Access the protected layer with not valid credentials
"""
pg_layer = self._getPostGISLayer('testlayer_èé')
self.assertFalse(pg_layer.isValid())
if __name__ == '__main__':
unittest.main()

View File

@ -170,6 +170,19 @@ class TestAuthManager(unittest.TestCase):
wms_layer = QgsRasterLayer(uri, layer_name, 'wms')
return wms_layer
@classmethod
def _getGeoJsonLayer(cls, type_name, layer_name=None, authcfg=None):
"""
OGR layer factory
"""
if layer_name is None:
layer_name = 'geojson_' + type_name
uri = '%s://%s:%s/?MAP=%s&SERVICE=WFS&REQUEST=GetFeature&TYPENAME=%s&VERSION=2.0.0&OUTPUTFORMAT=geojson' % (cls.protocol, cls.hostname, cls.port, cls.project_path, urllib.parse.quote(type_name))
if authcfg is not None:
uri += " authcfg='%s'" % authcfg
geojson_layer = QgsVectorLayer(uri, layer_name, 'ogr')
return geojson_layer
def testValidAuthAccess(self):
"""
Access the HTTP Basic protected layer with valid credentials
@ -178,6 +191,8 @@ class TestAuthManager(unittest.TestCase):
self.assertTrue(wfs_layer.isValid())
wms_layer = self._getWMSLayer('testlayer_èé', authcfg=self.auth_config.id())
self.assertTrue(wms_layer.isValid())
geojson_layer = self._getGeoJsonLayer('testlayer_èé', authcfg=self.auth_config.id())
self.assertTrue(geojson_layer.isValid())
def testInvalidAuthAccess(self):
"""
@ -187,6 +202,8 @@ class TestAuthManager(unittest.TestCase):
self.assertFalse(wfs_layer.isValid())
wms_layer = self._getWMSLayer('testlayer_èé')
self.assertFalse(wms_layer.isValid())
geojson_layer = self._getGeoJsonLayer('testlayer_èé')
self.assertFalse(geojson_layer.isValid())
def testInvalidAuthFileDownload(self):
"""