diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index 89a3110d9599..9c6af4a38d06 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -2549,6 +2549,22 @@ # -- Qgis.FileFilterType.baseClass = Qgis # monkey patching scoped based enum +Qgis.UriCleaningFlag.RemoveCredentials.__doc__ = "Completely remove credentials (eg passwords) from the URI. This flag is not compatible with the RedactCredentials flag." +Qgis.UriCleaningFlag.RedactCredentials.__doc__ = "Replace the value of credentials (eg passwords) with 'xxxxxxxx'. This flag is not compatible with the RemoveCredentials flag." +Qgis.UriCleaningFlag.__doc__ = """Flags for cleaning layer URIs. + +.. versionadded:: 3.42 + +* ``RemoveCredentials``: Completely remove credentials (eg passwords) from the URI. This flag is not compatible with the RedactCredentials flag. +* ``RedactCredentials``: Replace the value of credentials (eg passwords) with 'xxxxxxxx'. This flag is not compatible with the RemoveCredentials flag. + +""" +# -- +Qgis.UriCleaningFlag.baseClass = Qgis +Qgis.UriCleaningFlags = lambda flags=0: Qgis.UriCleaningFlag(flags) +Qgis.UriCleaningFlags.baseClass = Qgis +UriCleaningFlags = Qgis # dirty hack since SIP seems to introduce the flags in module +# monkey patching scoped based enum Qgis.SublayerQueryFlag.FastScan.__doc__ = "Indicates that the provider must scan for sublayers using the fastest possible approach -- e.g. by first checking that a uri has an extension which is known to be readable by the provider" Qgis.SublayerQueryFlag.ResolveGeometryType.__doc__ = "Attempt to resolve the geometry type for vector sublayers" Qgis.SublayerQueryFlag.CountFeatures.__doc__ = "Count features in vector sublayers" diff --git a/python/PyQt6/core/auto_generated/providers/qgsprovidermetadata.sip.in b/python/PyQt6/core/auto_generated/providers/qgsprovidermetadata.sip.in index 706c2f3912ab..df1051d9c0d4 100644 --- a/python/PyQt6/core/auto_generated/providers/qgsprovidermetadata.sip.in +++ b/python/PyQt6/core/auto_generated/providers/qgsprovidermetadata.sip.in @@ -573,6 +573,13 @@ If a provider does not work with paths, unmodified URI will be returned. .. seealso:: :py:func:`absoluteToRelativeUri` .. versionadded:: 3.30 +%End + + virtual QString cleanUri( const QString &uri, Qgis::UriCleaningFlags flags = Qgis::UriCleaningFlag::RemoveCredentials ) const; +%Docstring +Cleans a layer ``uri``, e.g. to remove or hide sensitive information from the URI. + +.. versionadded:: 3.42 %End virtual QList< QgsDataItemProvider * > dataItemProviders() const /Factory/; diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index 714c9e794b5e..5141594f875b 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -783,6 +783,15 @@ The development version TiledScene, }; + enum class UriCleaningFlag /BaseType=IntFlag/ + { + RemoveCredentials, + RedactCredentials, + }; + + typedef QFlags UriCleaningFlags; + + enum class SublayerQueryFlag /BaseType=IntFlag/ { FastScan, @@ -3313,6 +3322,8 @@ QFlags operator|(Qgis::SnappingType f1, QFlags operator|(Qgis::SqlLayerDefinitionCapability f1, QFlags f2); +QFlags operator|(Qgis::UriCleaningFlag f1, QFlags f2); + QFlags operator|(Qgis::SublayerFlag f1, QFlags f2); QFlags operator|(Qgis::SublayerQueryFlag f1, QFlags f2); diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 00a8c497c153..e2e8582f3bea 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -2521,6 +2521,21 @@ # -- Qgis.FileFilterType.baseClass = Qgis # monkey patching scoped based enum +Qgis.UriCleaningFlag.RemoveCredentials.__doc__ = "Completely remove credentials (eg passwords) from the URI. This flag is not compatible with the RedactCredentials flag." +Qgis.UriCleaningFlag.RedactCredentials.__doc__ = "Replace the value of credentials (eg passwords) with 'xxxxxxxx'. This flag is not compatible with the RemoveCredentials flag." +Qgis.UriCleaningFlag.__doc__ = """Flags for cleaning layer URIs. + +.. versionadded:: 3.42 + +* ``RemoveCredentials``: Completely remove credentials (eg passwords) from the URI. This flag is not compatible with the RedactCredentials flag. +* ``RedactCredentials``: Replace the value of credentials (eg passwords) with 'xxxxxxxx'. This flag is not compatible with the RemoveCredentials flag. + +""" +# -- +Qgis.UriCleaningFlag.baseClass = Qgis +Qgis.UriCleaningFlags.baseClass = Qgis +UriCleaningFlags = Qgis # dirty hack since SIP seems to introduce the flags in module +# monkey patching scoped based enum Qgis.SublayerQueryFlag.FastScan.__doc__ = "Indicates that the provider must scan for sublayers using the fastest possible approach -- e.g. by first checking that a uri has an extension which is known to be readable by the provider" Qgis.SublayerQueryFlag.ResolveGeometryType.__doc__ = "Attempt to resolve the geometry type for vector sublayers" Qgis.SublayerQueryFlag.CountFeatures.__doc__ = "Count features in vector sublayers" diff --git a/python/core/auto_generated/providers/qgsprovidermetadata.sip.in b/python/core/auto_generated/providers/qgsprovidermetadata.sip.in index 30f75c681884..12b10dcef83e 100644 --- a/python/core/auto_generated/providers/qgsprovidermetadata.sip.in +++ b/python/core/auto_generated/providers/qgsprovidermetadata.sip.in @@ -573,6 +573,13 @@ If a provider does not work with paths, unmodified URI will be returned. .. seealso:: :py:func:`absoluteToRelativeUri` .. versionadded:: 3.30 +%End + + virtual QString cleanUri( const QString &uri, Qgis::UriCleaningFlags flags = Qgis::UriCleaningFlag::RemoveCredentials ) const; +%Docstring +Cleans a layer ``uri``, e.g. to remove or hide sensitive information from the URI. + +.. versionadded:: 3.42 %End virtual QList< QgsDataItemProvider * > dataItemProviders() const /Factory/; diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index cfaf28f6283f..e11c4a2b9833 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -783,6 +783,15 @@ The development version TiledScene, }; + enum class UriCleaningFlag + { + RemoveCredentials, + RedactCredentials, + }; + + typedef QFlags UriCleaningFlags; + + enum class SublayerQueryFlag { FastScan, @@ -3313,6 +3322,8 @@ QFlags operator|(Qgis::SnappingType f1, QFlags operator|(Qgis::SqlLayerDefinitionCapability f1, QFlags f2); +QFlags operator|(Qgis::UriCleaningFlag f1, QFlags f2); + QFlags operator|(Qgis::SublayerFlag f1, QFlags f2); QFlags operator|(Qgis::SublayerQueryFlag f1, QFlags f2); diff --git a/src/core/providers/gdal/qgsgdalprovider.cpp b/src/core/providers/gdal/qgsgdalprovider.cpp index d91fd8ac6213..2c39fc087b4c 100644 --- a/src/core/providers/gdal/qgsgdalprovider.cpp +++ b/src/core/providers/gdal/qgsgdalprovider.cpp @@ -2720,6 +2720,29 @@ QString QgsGdalProviderMetadata::relativeToAbsoluteUri( const QString &uri, cons return context.pathResolver().readPath( src ); } +QString QgsGdalProviderMetadata::cleanUri( const QString &uri, Qgis::UriCleaningFlags flags ) const +{ + QVariantMap components = decodeUri( uri ); + QVariantMap credentialOptions = components.value( QStringLiteral( "credentialOptions" ) ).toMap(); + if ( !credentialOptions.empty() ) + { + if ( flags.testFlag( Qgis::UriCleaningFlag::RedactCredentials ) ) + { + for ( auto it = credentialOptions.begin(); it != credentialOptions.end(); ++it ) + { + it.value() = QStringLiteral( "XXXXXXXX" ); + } + components.insert( QStringLiteral( "credentialOptions" ), credentialOptions ); + } + else if ( flags.testFlag( Qgis::UriCleaningFlag::RemoveCredentials ) ) + { + components.remove( QStringLiteral( "credentialOptions" ) ); + } + } + + return encodeUri( components ); +} + bool QgsGdalProviderMetadata::uriIsBlocklisted( const QString &uri ) const { const QVariantMap parts = decodeUri( uri ); diff --git a/src/core/providers/gdal/qgsgdalprovider.h b/src/core/providers/gdal/qgsgdalprovider.h index 3bbff95a0e94..c2add6c120e2 100644 --- a/src/core/providers/gdal/qgsgdalprovider.h +++ b/src/core/providers/gdal/qgsgdalprovider.h @@ -390,6 +390,7 @@ class QgsGdalProviderMetadata final: public QgsProviderMetadata QString encodeUri( const QVariantMap &parts ) const override; QString absoluteToRelativeUri( const QString &uri, const QgsReadWriteContext &context ) const override; QString relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext &context ) const override; + QString cleanUri( const QString &uri, Qgis::UriCleaningFlags flags = Qgis::UriCleaningFlag::RemoveCredentials ) const override; bool uriIsBlocklisted( const QString &uri ) const override; QgsGdalProvider *createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags = Qgis::DataProviderReadFlags() ) override; QgsGdalProvider *createRasterDataProvider( diff --git a/src/core/providers/qgsprovidermetadata.cpp b/src/core/providers/qgsprovidermetadata.cpp index 8e71af778198..4c58f8dfe20c 100644 --- a/src/core/providers/qgsprovidermetadata.cpp +++ b/src/core/providers/qgsprovidermetadata.cpp @@ -198,6 +198,15 @@ QString QgsProviderMetadata::relativeToAbsoluteUri( const QString &uri, const Qg return context.pathResolver().readPath( uri ); } +QString QgsProviderMetadata::cleanUri( const QString &uri, Qgis::UriCleaningFlags flags ) const +{ + if ( flags.testFlag( Qgis::UriCleaningFlag::RemoveCredentials ) ) + return QgsDataSourceUri::removePassword( uri ); + else if ( flags.testFlag( Qgis::UriCleaningFlag::RedactCredentials ) ) + return QgsDataSourceUri::removePassword( uri, true ); + return uri; +} + Qgis::VectorExportResult QgsProviderMetadata::createEmptyLayer( const QString &, const QgsFields &, Qgis::WkbType, const QgsCoordinateReferenceSystem &, bool, QMap &, diff --git a/src/core/providers/qgsprovidermetadata.h b/src/core/providers/qgsprovidermetadata.h index ea8d3b82e3f1..e149a3eaf03f 100644 --- a/src/core/providers/qgsprovidermetadata.h +++ b/src/core/providers/qgsprovidermetadata.h @@ -612,6 +612,13 @@ class CORE_EXPORT QgsProviderMetadata : public QObject */ virtual QString relativeToAbsoluteUri( const QString &uri, const QgsReadWriteContext &context ) const; + /** + * Cleans a layer \a uri, e.g. to remove or hide sensitive information from the URI. + * + * \since QGIS 3.42 + */ + virtual QString cleanUri( const QString &uri, Qgis::UriCleaningFlags flags = Qgis::UriCleaningFlag::RemoveCredentials ) const; + /** * Returns data item providers. Caller is responsible for ownership of the item providers * \see QgsProviderGuiMetadata::dataItemGuiProviders() diff --git a/src/core/qgis.h b/src/core/qgis.h index 40a0c1affc01..f79eb35d8589 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -1293,6 +1293,26 @@ class CORE_EXPORT Qgis }; Q_ENUM( FileFilterType ) + /** + * Flags for cleaning layer URIs. + * + * \since QGIS 3.42 + */ + enum class UriCleaningFlag : int SIP_ENUM_BASETYPE( IntFlag ) + { + RemoveCredentials = 1 << 0, //!< Completely remove credentials (eg passwords) from the URI. This flag is not compatible with the RedactCredentials flag. + RedactCredentials = 1 << 1, //!< Replace the value of credentials (eg passwords) with 'xxxxxxxx'. This flag is not compatible with the RemoveCredentials flag. + }; + Q_ENUM( UriCleaningFlag ) + + /** + * Flags for cleaning layer URIs. + * + * \since QGIS 3.42 + */ + Q_DECLARE_FLAGS( UriCleaningFlags, UriCleaningFlag ) + Q_FLAG( UriCleaningFlags ) + /** * Flags which control how data providers will scan for sublayers in a dataset. * @@ -5721,6 +5741,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SelectionFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SettingsTreeNodeOptions ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SnappingTypes ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SqlLayerDefinitionCapabilities ) +Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::UriCleaningFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SublayerFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SublayerQueryFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::FeatureRendererFlags ) diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index d4b31630664d..30423bd73912 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -477,34 +477,16 @@ QString QgsMapLayer::publicSource( bool redactCredentials ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - QString safeName = mDataSource; - - if ( providerType() == QLatin1String( "gdal" ) ) - { - QVariantMap components = QgsProviderRegistry::instance()->decodeUri( providerType(), safeName ); - QVariantMap credentialOptions = components.value( QStringLiteral( "credentialOptions" ) ).toMap(); - if ( !credentialOptions.empty() ) - { - if ( redactCredentials ) - { - for ( auto it = credentialOptions.begin(); it != credentialOptions.end(); ++it ) - { - it.value() = QStringLiteral( "XXXXXXXX" ); - } - components.insert( QStringLiteral( "credentialOptions" ), credentialOptions ); - } - else - { - components.remove( QStringLiteral( "credentialOptions" ) ); - } - } - safeName = QgsProviderRegistry::instance()->encodeUri( providerType(), components ); - } - // Redo this every time we're asked for it, as we don't know if // dataSource has changed. - safeName = QgsDataSourceUri::removePassword( safeName, redactCredentials ); - return safeName; + if ( const QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( providerType() ) ) + { + return metadata->cleanUri( mDataSource, redactCredentials ? Qgis::UriCleaningFlag::RedactCredentials : Qgis::UriCleaningFlag::RemoveCredentials ); + } + else + { + return QgsDataSourceUri::removePassword( mDataSource, redactCredentials ); + } } QString QgsMapLayer::source() const