This is an automated email from the ASF dual-hosted git repository.

rusackas 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 5aaf9d337b5 fix(jinja): handle non-JSON body with JSON content-type in 
get_form_data (#42196)
5aaf9d337b5 is described below

commit 5aaf9d337b549c2d7e7dbde174d7f32a7d474566
Author: Amin Ghadersohi <[email protected]>
AuthorDate: Tue Jul 28 12:18:08 2026 -0400

    fix(jinja): handle non-JSON body with JSON content-type in get_form_data 
(#42196)
---
 superset/views/utils.py              | 24 +++++++++++++++-
 tests/unit_tests/views/test_utils.py | 53 ++++++++++++++++++++++++++++++++++++
 2 files changed, 76 insertions(+), 1 deletion(-)

diff --git a/superset/views/utils.py b/superset/views/utils.py
index 6fa24539701..804ee7ef262 100644
--- a/superset/views/utils.py
+++ b/superset/views/utils.py
@@ -35,6 +35,7 @@ from flask_appbuilder.security.sqla import models as ab_models
 from flask_appbuilder.security.sqla.models import User
 from flask_babel import _
 from sqlalchemy.exc import NoResultFound
+from werkzeug.exceptions import BadRequest
 
 from superset import appbuilder, dataframe, db, result_set, viz
 from superset.common.db_query_status import QueryStatus
@@ -197,6 +198,27 @@ def loads_request_json(request_json_data: str) -> 
dict[Any, Any]:
     return parsed if isinstance(parsed, dict) else {}
 
 
+def get_request_json_body() -> dict[Any, Any]:
+    """Parse the request body as JSON, coercing failures to ``{}``.
+
+    ``request.is_json`` only inspects the Content-Type header, not whether the
+    body is actually parseable JSON. Callers reaching ``get_form_data`` from a
+    non-HTTP-chart-data context (e.g. an MCP tool call rendering
+    ``filter_values()``) can have a request context whose Content-Type claims
+    JSON but whose body isn't a JSON chart-data payload, which makes Werkzeug
+    raise ``BadRequest`` from ``request.get_json()``. A well-formed but
+    non-object JSON body (e.g. ``null``, a scalar, or an array) is coerced to
+    ``{}`` too, since callers treat the result as a mapping.
+    """
+    if not request.is_json:
+        return {}
+    try:
+        data = request.get_json(cache=True)
+    except BadRequest:
+        return {}
+    return data if isinstance(data, dict) else {}
+
+
 #: Parameter names `url_for` interprets itself rather than appending to the
 #: query string. Request-supplied query keys matching these must never be
 #: forwarded as `url_for` kwargs.
@@ -345,7 +367,7 @@ def get_form_data(
     form_data: dict[str, Any] = initial_form_data or {}
 
     if has_request_context():
-        json_data = request.get_json(cache=True) if request.is_json else {}
+        json_data = get_request_json_body()
 
         # chart data API requests are JSON
         first_query = (
diff --git a/tests/unit_tests/views/test_utils.py 
b/tests/unit_tests/views/test_utils.py
new file mode 100644
index 00000000000..8da8ebf21f5
--- /dev/null
+++ b/tests/unit_tests/views/test_utils.py
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Tests for superset.views.utils module"""
+
+from flask import current_app
+
+from superset.views.utils import get_form_data
+
+
+def test_get_form_data_handles_non_json_body_with_json_content_type() -> None:
+    """get_form_data returns gracefully when Content-Type claims JSON but the
+    body isn't parseable JSON, instead of letting Werkzeug's BadRequest escape.
+
+    This is the shape of the request context an MCP tool call runs in when a
+    chart/dataset SQL template calls the ``filter_values()`` Jinja macro: the
+    Content-Type header says ``application/json`` but the body is not a JSON
+    chart-data payload.
+    """
+    with current_app.test_request_context(
+        data="not-json-at-all", content_type="application/json"
+    ):
+        form_data, slc = get_form_data()
+
+    assert form_data == {}
+    assert slc is None
+
+
+def test_get_form_data_handles_non_dict_json_body() -> None:
+    """get_form_data coerces a well-formed but non-object JSON body to {}.
+
+    ``request.get_json()`` happily returns a scalar or list for valid JSON
+    that isn't a JSON object (e.g. ``null`` or ``42``). Downstream code treats
+    the parsed body as a mapping, so a non-dict result must not leak through.
+    """
+    with current_app.test_request_context(data="42", 
content_type="application/json"):
+        form_data, slc = get_form_data()
+
+    assert form_data == {}
+    assert slc is None

Reply via email to