Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting VSI credentials when loading OGR/GDAL layers #57826

Merged
merged 19 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0ca2d7e
[gdal] Ensure cloud vsi prefixes are correctly decoded
nyalldawson Jun 19, 2024
219be78
[gdal/ogr] Allow storing credential key/value credential options in uris
nyalldawson Jun 19, 2024
ceea42d
Make protocol handling in OGR/GDAL source select more robust
nyalldawson Jun 19, 2024
781d568
Create QgsGdalCredentialOptionsWidget
nyalldawson Jun 20, 2024
ebb7d11
Add credential options to GDAL source select
nyalldawson Jun 19, 2024
21674d8
Handle default credential values
nyalldawson Jun 21, 2024
0a78dc3
Handle numeric option types
nyalldawson Jun 21, 2024
18b3497
Add separator before generic settings
nyalldawson Jun 21, 2024
f328e43
Only expose VSI cloud protocols which are actually available
nyalldawson Jun 21, 2024
bfd86b3
Move option handling out to a common place
nyalldawson Jun 21, 2024
e6f20a2
Move configuration option widget creation to common place
nyalldawson Jun 21, 2024
4ca1571
Move other GDAL GUI utility functions into QgsGdalGuiUtils
nyalldawson Jun 21, 2024
f9ea648
Condense duplicate code for creation of widgets from GDAL options
nyalldawson Jun 21, 2024
4ca1ab8
Move VSISetPathSpecificOption logic to common function
nyalldawson Jun 21, 2024
3f978dd
Add credential options to OGR source select
nyalldawson Jun 22, 2024
c5c4b5b
Sort protocols alphabetically in combo
nyalldawson Jun 22, 2024
1e95e7a
Move isProtocolCloudType to core utils class
nyalldawson Jun 22, 2024
995a9ae
[ogr] Handle VSI credentials in querySublayers
nyalldawson Jun 22, 2024
2c00e70
Fix window title check
nyalldawson Jun 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/providers/gdal/qgsgdalcredentialoptionswidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/





class QgsGdalCredentialOptionsWidget : QWidget
{
%Docstring(signature="appended")
A widget for configuring GDAL credential options.

.. versionadded:: 3.40
%End

%TypeHeaderCode
#include "qgsgdalcredentialoptionswidget.h"
%End
public:

QgsGdalCredentialOptionsWidget( QWidget *parent = 0 );
%Docstring
Constructor for QgsGdalCredentialOptionsWidget, with the specified ``parent`` widget.
%End

void setDriver( const QString &driver );
%Docstring
Sets the corresponding ``driver``.

This should match a GDAL VSI driver, eg "vsis3".
%End

QVariantMap credentialOptions() const;
%Docstring
Returns the credential options configured in the widget.

.. seealso:: :py:func:`setCredentialOptions`
%End

void setCredentialOptions( const QVariantMap &options );
%Docstring
Sets the credential ``options`` to show in the widget.

.. seealso:: :py:func:`credentialOptions`
%End

signals:

void optionsChanged();
%Docstring
Emitted when the credential options are changed in the widget.
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/providers/gdal/qgsgdalcredentialoptionswidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/PyQt6/gui/gui_auto.sip
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@
%Include auto_generated/proj/qgsprojectionselectionwidget.sip
%Include auto_generated/proj/qgsrecentcoordinatereferencesystemsmodel.sip
%Include auto_generated/providers/qgsabstractdbsourceselect.sip
%Include auto_generated/providers/gdal/qgsgdalcredentialoptionswidget.sip
%Include auto_generated/raster/qgsrasterattributetablewidget.sip
%Include auto_generated/raster/qgsrasterattributetabledialog.sip
%Include auto_generated/raster/qgscolorrampshaderwidget.sip
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/providers/gdal/qgsgdalcredentialoptionswidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/





class QgsGdalCredentialOptionsWidget : QWidget
{
%Docstring(signature="appended")
A widget for configuring GDAL credential options.

.. versionadded:: 3.40
%End

%TypeHeaderCode
#include "qgsgdalcredentialoptionswidget.h"
%End
public:

QgsGdalCredentialOptionsWidget( QWidget *parent = 0 );
%Docstring
Constructor for QgsGdalCredentialOptionsWidget, with the specified ``parent`` widget.
%End

void setDriver( const QString &driver );
%Docstring
Sets the corresponding ``driver``.

This should match a GDAL VSI driver, eg "vsis3".
%End

QVariantMap credentialOptions() const;
%Docstring
Returns the credential options configured in the widget.

.. seealso:: :py:func:`setCredentialOptions`
%End

void setCredentialOptions( const QVariantMap &options );
%Docstring
Sets the credential ``options`` to show in the widget.

.. seealso:: :py:func:`credentialOptions`
%End

signals:

void optionsChanged();
%Docstring
Emitted when the credential options are changed in the widget.
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/providers/gdal/qgsgdalcredentialoptionswidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/gui/gui_auto.sip
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@
%Include auto_generated/proj/qgsprojectionselectionwidget.sip
%Include auto_generated/proj/qgsrecentcoordinatereferencesystemsmodel.sip
%Include auto_generated/providers/qgsabstractdbsourceselect.sip
%Include auto_generated/providers/gdal/qgsgdalcredentialoptionswidget.sip
%Include auto_generated/raster/qgsrasterattributetablewidget.sip
%Include auto_generated/raster/qgsrasterattributetabledialog.sip
%Include auto_generated/raster/qgscolorrampshaderwidget.sip
Expand Down
50 changes: 47 additions & 3 deletions src/core/providers/gdal/qgsgdalproviderbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
#include "qgslogger.h"
#include "qgsgdalproviderbase.h"
#include "qgsgdalutils.h"
#include "qgssettings.h"

#include <mutex>
#include <QRegularExpression>
Expand Down Expand Up @@ -290,6 +289,21 @@ GDALDatasetH QgsGdalProviderBase::gdalOpen( const QString &uri, unsigned int nOp
option.toUtf8().constData() );
}

const QString vsiPrefix = parts.value( QStringLiteral( "vsiPrefix" ) ).toString();
const QString vsiSuffix = parts.value( QStringLiteral( "vsiSuffix" ) ).toString();

const QVariantMap credentialOptions = parts.value( QStringLiteral( "credentialOptions" ) ).toMap();
parts.remove( QStringLiteral( "credentialOptions" ) );
if ( !credentialOptions.isEmpty() && !vsiPrefix.isEmpty() )
{
const thread_local QRegularExpression bucketRx( QStringLiteral( "^(.*?)/" ) );
const QRegularExpressionMatch bucketMatch = bucketRx.match( parts.value( QStringLiteral( "path" ) ).toString() );
if ( bucketMatch.hasMatch() )
{
QgsGdalUtils::applyVsiCredentialOptions( vsiPrefix, bucketMatch.captured( 1 ), credentialOptions );
}
}

const bool modify_OGR_GPKG_FOREIGN_KEY_CHECK = !CPLGetConfigOption( "OGR_GPKG_FOREIGN_KEY_CHECK", nullptr );
if ( modify_OGR_GPKG_FOREIGN_KEY_CHECK )
{
Expand All @@ -301,8 +315,6 @@ GDALDatasetH QgsGdalProviderBase::gdalOpen( const QString &uri, unsigned int nOp

if ( !hDS )
{
const QString vsiPrefix = parts.value( QStringLiteral( "vsiPrefix" ) ).toString();
const QString vsiSuffix = parts.value( QStringLiteral( "vsiSuffix" ) ).toString();
if ( vsiSuffix.isEmpty() && QgsGdalUtils::isVsiArchivePrefix( vsiPrefix ) )
{
// in the case that a direct path to a vsi supported archive was specified BUT
Expand Down Expand Up @@ -391,6 +403,7 @@ QVariantMap QgsGdalProviderBase::decodeGdalUri( const QString &uri )
QString layerName;
QString authcfg;
QStringList openOptions;
QVariantMap credentialOptions;

const thread_local QRegularExpression authcfgRegex( " authcfg='([^']+)'" );
QRegularExpressionMatch match;
Expand Down Expand Up @@ -451,13 +464,35 @@ QVariantMap QgsGdalProviderBase::decodeGdalUri( const QString &uri )
break;
}
}

const thread_local QRegularExpression credentialOptionRegex( QStringLiteral( "\\|credential:([^|]*)" ) );
const thread_local QRegularExpression credentialOptionKeyValueRegex( QStringLiteral( "(.*?)=(.*)" ) );
while ( true )
{
const QRegularExpressionMatch match = credentialOptionRegex.match( path );
if ( match.hasMatch() )
{
const QRegularExpressionMatch keyValueMatch = credentialOptionKeyValueRegex.match( match.captured( 1 ) );
if ( keyValueMatch.hasMatch() )
{
credentialOptions.insert( keyValueMatch.captured( 1 ), keyValueMatch.captured( 2 ) );
}
path = path.remove( match.capturedStart( 0 ), match.capturedLength( 0 ) );
}
else
{
break;
}
}
}

QVariantMap uriComponents;
uriComponents.insert( QStringLiteral( "path" ), path );
uriComponents.insert( QStringLiteral( "layerName" ), layerName );
if ( !openOptions.isEmpty() )
uriComponents.insert( QStringLiteral( "openOptions" ), openOptions );
if ( !credentialOptions.isEmpty() )
uriComponents.insert( QStringLiteral( "credentialOptions" ), credentialOptions );
if ( !vsiPrefix.isEmpty() )
uriComponents.insert( QStringLiteral( "vsiPrefix" ), vsiPrefix );
if ( !vsiSuffix.isEmpty() )
Expand Down Expand Up @@ -494,6 +529,15 @@ QString QgsGdalProviderBase::encodeGdalUri( const QVariantMap &parts )
uri += openOption;
}

const QVariantMap credentialOptions = parts.value( QStringLiteral( "credentialOptions" ) ).toMap();
for ( auto it = credentialOptions.constBegin(); it != credentialOptions.constEnd(); ++it )
{
if ( !it.value().toString().isEmpty() )
{
uri += QStringLiteral( "|credential:%1=%2" ).arg( it.key(), it.value().toString() );
}
}

if ( !authcfg.isEmpty() )
uri += QStringLiteral( " authcfg='%1'" ).arg( authcfg );

Expand Down
18 changes: 15 additions & 3 deletions src/core/providers/ogr/qgsogrprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,21 +418,33 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio

QgsDebugMsgLevel( "Data source uri is [" + uri + ']', 2 );

QVariantMap credentialOptions;
mFilePath = QgsOgrProviderUtils::analyzeURI( uri,
mIsSubLayer,
mLayerIndex,
mLayerName,
mSubsetString,
mOgrGeometryTypeFilter,
mOpenOptions );

mOpenOptions,
credentialOptions );

const QVariantMap parts = QgsOgrProviderMetadata().decodeUri( uri );
if ( parts.contains( QStringLiteral( "uniqueGeometryType" ) ) )
{
mUniqueGeometryType = parts.value( QStringLiteral( "uniqueGeometryType" ) ).toString() == QLatin1String( "yes" );
}

const QString vsiPrefix = parts.value( QStringLiteral( "vsiPrefix" ) ).toString();
if ( !credentialOptions.isEmpty() && !vsiPrefix.isEmpty() )
{
const thread_local QRegularExpression bucketRx( QStringLiteral( "^(.*?)/" ) );
const QRegularExpressionMatch bucketMatch = bucketRx.match( parts.value( QStringLiteral( "path" ) ).toString() );
if ( bucketMatch.hasMatch() )
{
QgsGdalUtils::applyVsiCredentialOptions( vsiPrefix, bucketMatch.captured( 1 ), credentialOptions );
}
}

// to be called only after mFilePath has been set
invalidateNetworkCache();

Expand Down Expand Up @@ -4193,7 +4205,7 @@ void QgsOgrProvider::open( OpenMode mode )
// Try to open using VSIFileHandler
// see http://trac.osgeo.org/gdal/wiki/UserDocs/ReadInZip
const QString vsiPrefix = QgsGdalUtils::vsiPrefixForPath( dataSourceUri( true ) );
if ( !vsiPrefix.isEmpty() || mFilePath.startsWith( QLatin1String( "/vsicurl/" ) ) )
if ( ( !vsiPrefix.isEmpty() && vsiPrefix != QStringLiteral( "/vsimem/" ) ) || mFilePath.startsWith( QLatin1String( "/vsicurl/" ) ) )
{
// GDAL>=1.8.0 has write support for zip, but read and write operations
// cannot be interleaved, so for now just use read-only.
Expand Down
Loading
Loading