Compare commits

...

2 Commits

Author SHA1 Message Date
Evan
004df4b89a fix(chart): add missing mock parameter in test_query_context_update_requires_chart_access
Three @patch decorators but only two function parameters meant the mock for
superset.commands.chart.update.g was never captured and its .user was never
set. Added mock_u_g as the third parameter and set mock_u_g.user = gamma so
all three g objects are consistent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 18:27:00 -07:00
Claude Code
632f8702c4 fix(chart): require chart access for query_context-only updates
UpdateChartCommand skips the ownership check for "query_context-only" updates
(payload == {query_context, query_context_generation:true}) so report workers
and the UI's lazy query_context backfill can run as non-owners. But it skipped
ALL authorization, so any user with can_write on Chart could rewrite the
query_context of a chart they don't own (CWE-639).

Replace the unconditional skip with security_manager.raise_for_access(chart=...)
on that path. That still permits the legitimate non-owner flows (admins, owners,
and any user with access to the chart's datasource — which includes viewers who
can render the chart and the report executor), while rejecting users who cannot
access the chart at all.

DRAFT: needs CI / manual validation that the report-execution screenshot path
(executor user) passes raise_for_access in all configurations before merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 17:24:52 -07:00
2 changed files with 41 additions and 2 deletions

View File

@@ -112,8 +112,13 @@ class UpdateChartCommand(UpdateMixin, BaseCommand):
if not self._model:
raise ChartNotFoundError()
# Check and update ownership; when only updating query context we ignore
# ownership so the update can be performed by report workers
# Check and update ownership; when only updating query context we relax
# the ownership requirement so that non-owners (report workers and any
# viewer whose UI lazily backfills a missing query_context) can perform
# the update. We still require access to the chart in that case, so a
# user cannot rewrite the query_context of a chart they cannot access
# (raise_for_access permits admins, owners, and users with access to the
# chart's datasource).
if not is_query_context_update(self._properties):
try:
security_manager.raise_for_ownership(self._model)
@@ -126,6 +131,11 @@ class UpdateChartCommand(UpdateMixin, BaseCommand):
raise ChartForbiddenError() from ex
except ValidationError as ex:
exceptions.append(ex)
else:
try:
security_manager.raise_for_access(chart=self._model)
except SupersetSecurityException as ex:
raise ChartForbiddenError() from ex
# validate tags
try:

View File

@@ -25,6 +25,7 @@ from flask import g # noqa: F401
from superset import db, security_manager
from superset.commands.chart.create import CreateChartCommand
from superset.commands.chart.exceptions import (
ChartForbiddenError,
ChartNotFoundError,
WarmUpCacheChartNotFoundError,
)
@@ -452,6 +453,34 @@ class TestChartsUpdateCommand(SupersetTestCase):
assert len(chart.owners) == 1
assert chart.owners[0] == admin
@patch("superset.commands.chart.update.g")
@patch("superset.utils.core.g")
@patch("superset.security.manager.g")
@pytest.mark.usefixtures("load_energy_table_with_slice")
def test_query_context_update_requires_chart_access(
self, mock_sm_g, mock_g, mock_u_g
):
"""
A query_context-only update relaxes the ownership requirement but must
still require access to the chart: a non-owner without access to the
chart's datasource is rejected with ChartForbiddenError.
"""
chart = db.session.query(Slice).all()[0]
pk = chart.id
admin = security_manager.find_user(username="admin")
chart.owners = [admin]
db.session.commit()
# gamma has no access to the energy datasource and does not own the chart
gamma = security_manager.find_user(username="gamma")
mock_g.user = mock_sm_g.user = mock_u_g.user = gamma
json_obj = {
"query_context_generation": True,
"query_context": json.dumps({"foo": "bar"}),
}
with pytest.raises(ChartForbiddenError):
UpdateChartCommand(pk, json_obj).run()
@patch("superset.commands.chart.update.g")
@patch("superset.utils.core.g")
@patch("superset.security.manager.g")