This is an automated email from the ASF dual-hosted git repository.
vavila pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 228b5984096 feat: Labels for encrypted fields (#38075)
228b5984096 is described below
commit 228b5984096193a84b4ae932c17f2344936a466a
Author: Vitor Avila <[email protected]>
AuthorDate: Mon Feb 23 13:23:33 2026 -0300
feat: Labels for encrypted fields (#38075)
---
superset/db_engine_specs/base.py | 29 ++++++++++++++---
superset/db_engine_specs/bigquery.py | 4 ++-
superset/db_engine_specs/gsheets.py | 4 +--
superset/db_engine_specs/mysql.py | 4 +--
superset/db_engine_specs/postgres.py | 4 +--
superset/db_engine_specs/redshift.py | 4 +--
superset/db_engine_specs/snowflake.py | 4 +--
superset/db_engine_specs/ydb.py | 5 ++-
tests/unit_tests/db_engine_specs/test_base.py | 45 +++++++++++++++++++++++++++
9 files changed, 86 insertions(+), 17 deletions(-)
diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py
index 51a6a8778ba..fb0e26e77e7 100644
--- a/superset/db_engine_specs/base.py
+++ b/superset/db_engine_specs/base.py
@@ -532,10 +532,13 @@ class BaseEngineSpec: # pylint:
disable=too-many-public-methods
Pattern[str], tuple[str, SupersetErrorType, dict[str, Any]]
] = {}
- # List of JSON path to fields in `encrypted_extra` that should be masked
when the
- # database is edited. By default everything is masked.
+ # JSONPath fields in `encrypted_extra` that should be masked when the
database is
+ # edited. Can be a set of paths (labels will default to the path) or a
dict mapping
+ # paths to human-readable labels for import validation error messages.
# pylint: disable=invalid-name
- encrypted_extra_sensitive_fields: set[str] = {"$.*"}
+ encrypted_extra_sensitive_fields: set[str] | dict[str, str] = {
+ "$.*": "Encrypted Extra",
+ }
# Whether the engine supports file uploads
# if True, database will be listed as option in the upload file form
@@ -580,6 +583,22 @@ class BaseEngineSpec: # pylint:
disable=too-many-public-methods
# the `cancel_query` value in the `extra` field of the `query` object
has_query_id_before_execute = True
+ @classmethod
+ def encrypted_extra_sensitive_field_paths(cls) -> set[str]:
+ """
+ Returns a set of paths for fields that should be masked in the
+ ``masked_encrypted_extra`` JSON.
+
+ :param cls: Description
+ :return: Description
+ :rtype: set[str]
+ """
+ return (
+ set(cls.encrypted_extra_sensitive_fields)
+ if isinstance(cls.encrypted_extra_sensitive_fields, dict)
+ else cls.encrypted_extra_sensitive_fields
+ )
+
@classmethod
def get_rls_method(cls) -> RLSMethod:
"""
@@ -2443,7 +2462,7 @@ class BaseEngineSpec: # pylint:
disable=too-many-public-methods
masked_encrypted_extra = redact_sensitive(
config,
- cls.encrypted_extra_sensitive_fields,
+ cls.encrypted_extra_sensitive_field_paths(),
)
return json.dumps(masked_encrypted_extra)
@@ -2469,7 +2488,7 @@ class BaseEngineSpec: # pylint:
disable=too-many-public-methods
new_config = reveal_sensitive(
old_config,
new_config,
- cls.encrypted_extra_sensitive_fields,
+ cls.encrypted_extra_sensitive_field_paths(),
)
return json.dumps(new_config)
diff --git a/superset/db_engine_specs/bigquery.py
b/superset/db_engine_specs/bigquery.py
index 1284464ab7c..8ba5e99b389 100644
--- a/superset/db_engine_specs/bigquery.py
+++ b/superset/db_engine_specs/bigquery.py
@@ -191,7 +191,9 @@ class BigQueryEngineSpec(BaseEngineSpec): # pylint:
disable=too-many-public-met
# when editing the database, mask this field in `encrypted_extra`
# pylint: disable=invalid-name
- encrypted_extra_sensitive_fields = {"$.credentials_info.private_key"}
+ encrypted_extra_sensitive_fields = {
+ "$.credentials_info.private_key": "Service Account Private Key",
+ }
"""
https://www.python.org/dev/peps/pep-0249/#arraysize
diff --git a/superset/db_engine_specs/gsheets.py
b/superset/db_engine_specs/gsheets.py
index 9692db9d9a6..3bcb3a8c871 100644
--- a/superset/db_engine_specs/gsheets.py
+++ b/superset/db_engine_specs/gsheets.py
@@ -130,8 +130,8 @@ class GSheetsEngineSpec(ShillelaghEngineSpec):
# when editing the database, mask this field in `encrypted_extra`
# pylint: disable=invalid-name
encrypted_extra_sensitive_fields = {
- "$.service_account_info.private_key",
- "$.oauth2_client_info.secret",
+ "$.service_account_info.private_key": "Service Account Private Key",
+ "$.oauth2_client_info.secret": "OAuth2 Client Secret",
}
custom_errors: dict[Pattern[str], tuple[str, SupersetErrorType, dict[str,
Any]]] = {
diff --git a/superset/db_engine_specs/mysql.py
b/superset/db_engine_specs/mysql.py
index b6cba3906a6..99bf203f9a4 100644
--- a/superset/db_engine_specs/mysql.py
+++ b/superset/db_engine_specs/mysql.py
@@ -307,8 +307,8 @@ class MySQLEngineSpec(BasicParametersMixin, BaseEngineSpec):
# This follows the pattern used by other engine specs (bigquery,
snowflake, etc.)
# that specify exact paths rather than using the base class's catch-all
"$.*".
encrypted_extra_sensitive_fields = {
- "$.aws_iam.external_id",
- "$.aws_iam.role_arn",
+ "$.aws_iam.external_id": "AWS IAM External ID",
+ "$.aws_iam.role_arn": "AWS IAM Role ARN",
}
@staticmethod
diff --git a/superset/db_engine_specs/postgres.py
b/superset/db_engine_specs/postgres.py
index c407e3d7fb1..b744b7b440b 100644
--- a/superset/db_engine_specs/postgres.py
+++ b/superset/db_engine_specs/postgres.py
@@ -464,8 +464,8 @@ class PostgresEngineSpec(BasicParametersMixin,
PostgresBaseEngineSpec):
# This follows the pattern used by other engine specs (bigquery,
snowflake, etc.)
# that specify exact paths rather than using the base class's catch-all
"$.*".
encrypted_extra_sensitive_fields = {
- "$.aws_iam.external_id",
- "$.aws_iam.role_arn",
+ "$.aws_iam.external_id": "AWS IAM External ID",
+ "$.aws_iam.role_arn": "AWS IAM Role ARN",
}
column_type_mappings = (
diff --git a/superset/db_engine_specs/redshift.py
b/superset/db_engine_specs/redshift.py
index fcdfab16967..8621873e75b 100644
--- a/superset/db_engine_specs/redshift.py
+++ b/superset/db_engine_specs/redshift.py
@@ -206,8 +206,8 @@ class RedshiftEngineSpec(BasicParametersMixin,
PostgresBaseEngineSpec):
# This follows the pattern used by other engine specs (bigquery,
snowflake, etc.)
# that specify exact paths rather than using the base class's catch-all
"$.*".
encrypted_extra_sensitive_fields = {
- "$.aws_iam.external_id",
- "$.aws_iam.role_arn",
+ "$.aws_iam.external_id": "AWS IAM External ID",
+ "$.aws_iam.role_arn": "AWS IAM Role ARN",
}
@staticmethod
diff --git a/superset/db_engine_specs/snowflake.py
b/superset/db_engine_specs/snowflake.py
index 3f541699f05..7008b8fa028 100644
--- a/superset/db_engine_specs/snowflake.py
+++ b/superset/db_engine_specs/snowflake.py
@@ -151,8 +151,8 @@ class SnowflakeEngineSpec(PostgresBaseEngineSpec):
# pylint: disable=invalid-name
encrypted_extra_sensitive_fields = {
- "$.auth_params.privatekey_body",
- "$.auth_params.privatekey_pass",
+ "$.auth_params.privatekey_body": "Private Key Body",
+ "$.auth_params.privatekey_pass": "Private Key Password",
}
_time_grain_expressions = {
diff --git a/superset/db_engine_specs/ydb.py b/superset/db_engine_specs/ydb.py
index 07c0b3d5571..9ea1b5dd195 100755
--- a/superset/db_engine_specs/ydb.py
+++ b/superset/db_engine_specs/ydb.py
@@ -43,7 +43,10 @@ class YDBEngineSpec(BaseEngineSpec):
sqlalchemy_uri_placeholder = "ydb://{host}:{port}/{database_name}"
# pylint: disable=invalid-name
- encrypted_extra_sensitive_fields = {"$.connect_args.credentials",
"$.credentials"}
+ encrypted_extra_sensitive_fields = {
+ "$.connect_args.credentials": "Connection Credentials",
+ "$.credentials": "Credentials",
+ }
disable_ssh_tunneling = False
diff --git a/tests/unit_tests/db_engine_specs/test_base.py
b/tests/unit_tests/db_engine_specs/test_base.py
index 4f4409c1b99..6c6b98e0593 100644
--- a/tests/unit_tests/db_engine_specs/test_base.py
+++ b/tests/unit_tests/db_engine_specs/test_base.py
@@ -360,6 +360,51 @@ def test_unmask_encrypted_extra() -> None:
)
[email protected](
+ "masked_encrypted_extra,expected_result",
+ [
+ (
+ {
+ "$.credentials_info.private_key": "Private Key",
+ "$.access_token": "Access Token",
+ },
+ {
+ "$.credentials_info.private_key",
+ "$.access_token",
+ },
+ ),
+ (
+ {
+ "$.credentials_info.private_key",
+ "$.access_token",
+ },
+ {
+ "$.credentials_info.private_key",
+ "$.access_token",
+ },
+ ),
+ (
+ None,
+ {"$.*"},
+ ),
+ ],
+)
+def test_encrypted_extra_sensitive_field_paths_from_dict(
+ masked_encrypted_extra: set[str] | dict[str, str] | None,
+ expected_result: set[str],
+) -> None:
+ """
+ Test that `encrypted_extra_sensitive_field_paths` extracts the keys
+ when `encrypted_extra_sensitive_fields` is a dict.
+ """
+
+ class DictFieldsSpec(BaseEngineSpec):
+ if masked_encrypted_extra:
+ encrypted_extra_sensitive_fields = masked_encrypted_extra
+
+ assert DictFieldsSpec.encrypted_extra_sensitive_field_paths() ==
expected_result
+
+
def test_impersonate_user_backwards_compatible(mocker: MockerFixture) -> None:
"""
Test that the `impersonate_user` method calls the original methods it
replaced.