Move provider-specific credential redaction logic to QgsProviderMetadata

Followup 1907ab3
This commit is contained in:
Nyall Dawson 2025-02-03 09:28:15 +10:00
parent 0a87489756
commit d4c592e6d0
12 changed files with 136 additions and 26 deletions

View File

@ -2549,6 +2549,22 @@ Prior to QGIS 3.32 this was available as :py:class:`QgsProviderMetadata`.FilterT
# --
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"

View File

@ -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/;

View File

@ -783,6 +783,15 @@ The development version
TiledScene,
};
enum class UriCleaningFlag /BaseType=IntFlag/
{
RemoveCredentials,
RedactCredentials,
};
typedef QFlags<Qgis::UriCleaningFlag> UriCleaningFlags;
enum class SublayerQueryFlag /BaseType=IntFlag/
{
FastScan,
@ -3406,6 +3415,8 @@ QFlags<Qgis::SnappingType> operator|(Qgis::SnappingType f1, QFlags<Qgis::Snappin
QFlags<Qgis::SqlLayerDefinitionCapability> operator|(Qgis::SqlLayerDefinitionCapability f1, QFlags<Qgis::SqlLayerDefinitionCapability> f2);
QFlags<Qgis::UriCleaningFlag> operator|(Qgis::UriCleaningFlag f1, QFlags<Qgis::UriCleaningFlag> f2);
QFlags<Qgis::SublayerFlag> operator|(Qgis::SublayerFlag f1, QFlags<Qgis::SublayerFlag> f2);
QFlags<Qgis::SublayerQueryFlag> operator|(Qgis::SublayerQueryFlag f1, QFlags<Qgis::SublayerQueryFlag> f2);

View File

@ -2521,6 +2521,21 @@ Prior to QGIS 3.32 this was available as :py:class:`QgsProviderMetadata`.FilterT
# --
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"

View File

@ -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/;

View File

@ -783,6 +783,15 @@ The development version
TiledScene,
};
enum class UriCleaningFlag
{
RemoveCredentials,
RedactCredentials,
};
typedef QFlags<Qgis::UriCleaningFlag> UriCleaningFlags;
enum class SublayerQueryFlag
{
FastScan,
@ -3406,6 +3415,8 @@ QFlags<Qgis::SnappingType> operator|(Qgis::SnappingType f1, QFlags<Qgis::Snappin
QFlags<Qgis::SqlLayerDefinitionCapability> operator|(Qgis::SqlLayerDefinitionCapability f1, QFlags<Qgis::SqlLayerDefinitionCapability> f2);
QFlags<Qgis::UriCleaningFlag> operator|(Qgis::UriCleaningFlag f1, QFlags<Qgis::UriCleaningFlag> f2);
QFlags<Qgis::SublayerFlag> operator|(Qgis::SublayerFlag f1, QFlags<Qgis::SublayerFlag> f2);
QFlags<Qgis::SublayerQueryFlag> operator|(Qgis::SublayerQueryFlag f1, QFlags<Qgis::SublayerQueryFlag> f2);

View File

@ -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 );

View File

@ -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(

View File

@ -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<int, int> &,

View File

@ -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()

View File

@ -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.
*
@ -5894,6 +5914,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 )

View File

@ -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