Compare commits

...
Author SHA1 Message Date
Elizabeth ThompsonandClaude Opus 4.6 bcaf7b2e95 fix(database): reject non-dict encrypted_extra at schema validation layer
encrypted_extra_validator only checked that the value was valid JSON but
did not verify the decoded value was a dict.  Non-dict JSON values
(int, null, list, bool, bare string) passed validation and later caused
an AttributeError in _handle_oauth2() when .get() was called on a
non-mapping, surfacing as an opaque 500 instead of a 422.

Add an isinstance(…, dict) guard mirroring the identical fix already
applied to extra_validator in #44092.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-10 16:43:24 +00:00
2 changed files with 32 additions and 1 deletions
+11 -1
View File
@@ -245,12 +245,22 @@ def encrypted_extra_validator(value: str | None) -> None:
"""
if value:
try:
json.loads(value)
encrypted_extra = json.loads(value)
except json.JSONDecodeError as ex:
raise ValidationError(
[_("Field cannot be decoded by JSON. %(msg)s", msg=str(ex))]
) from ex
if not isinstance(encrypted_extra, dict):
raise ValidationError(
[
_(
"Encrypted extra field must be a mapping"
" from string keys to values."
)
]
)
def masked_encrypted_extra_validator(value: str) -> None:
"""
@@ -614,6 +614,27 @@ def test_extra_validator_rejects_non_dict_top_level_value(value: Any) -> None:
assert "must be a mapping" in str(exc_info.value)
@pytest.mark.parametrize("value", [123, None, [1, 2], True, "abc"])
def test_encrypted_extra_validator_rejects_non_dict_top_level_value(
value: Any,
) -> None:
"""
Test that encrypted_extra_validator rejects a top-level value that is valid
JSON but not a mapping (int, null, list, bool, string), instead of letting
AttributeError propagate from downstream .get() calls.
"""
from superset.databases.schemas import DatabasePostSchema
schema = DatabasePostSchema()
payload = {
"database_name": "test_db",
"masked_encrypted_extra": json.dumps(value),
}
with pytest.raises(ValidationError) as exc_info:
schema.load(payload)
assert "must be a mapping" in str(exc_info.value)
def test_cache_timeout_rejects_values_below_minus_one() -> None:
"""
Test that cache_timeout rejects values less than -1.