tien238lnd commented on code in PR #44338:
URL: https://github.com/apache/superset/pull/44338#discussion_r4033097060


##########
superset/mcp_service/chart/tool/get_chart_info.py:
##########
@@ -86,6 +91,106 @@ def _build_unsaved_chart_info(form_data_key: str) -> 
ChartInfo | ChartError:
     )
 
 
+def _get_explore_permalink(
+    permalink_key: str,
+) -> ExplorePermalinkValue | ChartError:
+    """Read an Explore permalink, enforcing the same access checks as Explore.
+
+    ``GetExplorePermalinkCommand`` checks access to the permalink's datasource
+    and, when it references a saved chart, to that chart.
+    """
+    from superset.commands.explore.permalink.get import 
GetExplorePermalinkCommand
+
+    try:
+        value = GetExplorePermalinkCommand(permalink_key).run()
+    except ForbiddenError:
+        return ChartError(
+            error="You do not have access to the chart or dataset in this 
permalink.",
+            error_type="PermalinkAccessDenied",
+        )
+    except (CommandException, SQLAlchemyError, ValidationError, ValueError) as 
ex:
+        # ValidationError: the permalink's datasource no longer exists or has 
an
+        # invalid type (raised by the access check).
+        logger.warning("Failed to read explore permalink: %s", ex)
+        return ChartError(
+            error="The explore permalink could not be read. Check the key.",
+            error_type="InvalidPermalink",
+        )
+    if not value:
+        return ChartError(
+            error="No explore permalink found for permalink_key.",
+            error_type="NotFound",
+        )
+    return value
+
+
+def _permalink_chart_id(permalink: ExplorePermalinkValue) -> int | None:
+    """Return the saved chart a permalink was created from, if any.
+
+    ``chartId`` is copied from the client-supplied ``formData.slice_id``, so
+    it is not guaranteed to be an int.
+    """
+    try:
+        return int(permalink.get("chartId") or 0) or None
+    except (TypeError, ValueError):
+        return None
+
+
+def _permalink_form_data(permalink: ExplorePermalinkValue) -> dict[str, Any]:
+    state = permalink.get("state")
+    form_data = state.get("formData") if isinstance(state, dict) else None
+    return dict(form_data) if isinstance(form_data, dict) else {}
+
+
+def _permalink_datasource(
+    permalink: ExplorePermalinkValue,
+) -> tuple[str | None, str | None]:
+    """Return the (name, type) of the datasource a permalink was built on.
+
+    A permalink's form_data carries the datasource as an opaque "<id>__<type>"
+    string, so the name has to be resolved from the ids the permalink stores
+    alongside it. Access to that datasource was already checked by
+    ``GetExplorePermalinkCommand``.
+    """
+    datasource_type = permalink.get("datasourceType") or 
DatasourceType.TABLE.value
+    datasource_id = permalink.get("datasourceId") or permalink.get("datasetId")
+    if not datasource_id:
+        return None, str(datasource_type)
+    try:
+        from superset.daos.datasource import DatasourceDAO
+
+        datasource = DatasourceDAO.get_datasource(
+            datasource_type=DatasourceType(datasource_type),
+            database_id_or_uuid=datasource_id,
+        )
+    except Exception:  # noqa: BLE001
+        # A deleted or unsupported datasource must not sink the whole read;
+        # the rest of the permalink state is still worth returning.
+        logger.warning(
+            "Could not resolve permalink datasource %s of type %s",
+            datasource_id,
+            datasource_type,
+        )
+        return None, str(datasource_type)
+    return datasource.datasource_name, str(datasource_type)

Review Comment:
   Fixed in 222adb3d14 with your fallback, unchanged. 
`test_permalink_without_saved_chart_names_its_sql_lab_query` builds the 
permalink on `5__query` and has `get_datasource` return a real 
`Query(tab_name="Untitled Query")`; it fails on the previous commit and passes 
now. The existing table-name test also uses a real `SqlaTable` now instead of a 
`MagicMock`, for the same reason.
   



##########
superset/mcp_service/chart/tool/get_chart_info.py:
##########
@@ -86,6 +91,106 @@ def _build_unsaved_chart_info(form_data_key: str) -> 
ChartInfo | ChartError:
     )
 
 
+def _get_explore_permalink(
+    permalink_key: str,
+) -> ExplorePermalinkValue | ChartError:
+    """Read an Explore permalink, enforcing the same access checks as Explore.
+
+    ``GetExplorePermalinkCommand`` checks access to the permalink's datasource
+    and, when it references a saved chart, to that chart.
+    """
+    from superset.commands.explore.permalink.get import 
GetExplorePermalinkCommand
+
+    try:
+        value = GetExplorePermalinkCommand(permalink_key).run()
+    except ForbiddenError:
+        return ChartError(
+            error="You do not have access to the chart or dataset in this 
permalink.",
+            error_type="PermalinkAccessDenied",
+        )
+    except (CommandException, SQLAlchemyError, ValidationError, ValueError) as 
ex:

Review Comment:
   Fixed in 222adb3d14: `SupersetSecurityException` now maps to 
`PermalinkAccessDenied`, next to `ForbiddenError`. Covered by 
`test_permalink_query_access_denied`.
   



##########
superset/mcp_service/chart/tool/get_chart_info.py:
##########
@@ -86,6 +91,106 @@ def _build_unsaved_chart_info(form_data_key: str) -> 
ChartInfo | ChartError:
     )
 
 
+def _get_explore_permalink(
+    permalink_key: str,
+) -> ExplorePermalinkValue | ChartError:
+    """Read an Explore permalink, enforcing the same access checks as Explore.
+
+    ``GetExplorePermalinkCommand`` checks access to the permalink's datasource
+    and, when it references a saved chart, to that chart.
+    """
+    from superset.commands.explore.permalink.get import 
GetExplorePermalinkCommand
+
+    try:
+        value = GetExplorePermalinkCommand(permalink_key).run()
+    except ForbiddenError:
+        return ChartError(
+            error="You do not have access to the chart or dataset in this 
permalink.",
+            error_type="PermalinkAccessDenied",
+        )
+    except (CommandException, SQLAlchemyError, ValidationError, ValueError) as 
ex:

Review Comment:
   Fixed in 222adb3d14: `SupersetTemplateException` now maps to 
`InvalidPermalink`, and the error message does not echo the template. Covered 
by `test_permalink_query_with_template_error`.
   



##########
superset/mcp_service/chart/tool/get_chart_info.py:
##########
@@ -86,6 +91,106 @@ def _build_unsaved_chart_info(form_data_key: str) -> 
ChartInfo | ChartError:
     )
 
 
+def _get_explore_permalink(
+    permalink_key: str,
+) -> ExplorePermalinkValue | ChartError:
+    """Read an Explore permalink, enforcing the same access checks as Explore.
+
+    ``GetExplorePermalinkCommand`` checks access to the permalink's datasource
+    and, when it references a saved chart, to that chart.
+    """
+    from superset.commands.explore.permalink.get import 
GetExplorePermalinkCommand
+
+    try:
+        value = GetExplorePermalinkCommand(permalink_key).run()
+    except ForbiddenError:
+        return ChartError(
+            error="You do not have access to the chart or dataset in this 
permalink.",
+            error_type="PermalinkAccessDenied",
+        )
+    except (CommandException, SQLAlchemyError, ValidationError, ValueError) as 
ex:

Review Comment:
   Same as the Copilot thread on this line: handled in 222adb3d14, 
`SupersetTemplateException` maps to `InvalidPermalink`.
   



##########
superset/mcp_service/chart/tool/get_chart_info.py:
##########
@@ -86,6 +91,106 @@ def _build_unsaved_chart_info(form_data_key: str) -> 
ChartInfo | ChartError:
     )
 
 
+def _get_explore_permalink(
+    permalink_key: str,
+) -> ExplorePermalinkValue | ChartError:
+    """Read an Explore permalink, enforcing the same access checks as Explore.
+
+    ``GetExplorePermalinkCommand`` checks access to the permalink's datasource
+    and, when it references a saved chart, to that chart.
+    """
+    from superset.commands.explore.permalink.get import 
GetExplorePermalinkCommand
+
+    try:
+        value = GetExplorePermalinkCommand(permalink_key).run()
+    except ForbiddenError:
+        return ChartError(
+            error="You do not have access to the chart or dataset in this 
permalink.",
+            error_type="PermalinkAccessDenied",
+        )
+    except (CommandException, SQLAlchemyError, ValidationError, ValueError) as 
ex:
+        # ValidationError: the permalink's datasource no longer exists or has 
an
+        # invalid type (raised by the access check).
+        logger.warning("Failed to read explore permalink: %s", ex)
+        return ChartError(
+            error="The explore permalink could not be read. Check the key.",
+            error_type="InvalidPermalink",
+        )
+    if not value:
+        return ChartError(
+            error="No explore permalink found for permalink_key.",
+            error_type="NotFound",
+        )
+    return value
+
+
+def _permalink_chart_id(permalink: ExplorePermalinkValue) -> int | None:
+    """Return the saved chart a permalink was created from, if any.
+
+    ``chartId`` is copied from the client-supplied ``formData.slice_id``, so
+    it is not guaranteed to be an int.
+    """
+    try:
+        return int(permalink.get("chartId") or 0) or None
+    except (TypeError, ValueError):
+        return None
+
+
+def _permalink_form_data(permalink: ExplorePermalinkValue) -> dict[str, Any]:
+    state = permalink.get("state")
+    form_data = state.get("formData") if isinstance(state, dict) else None
+    return dict(form_data) if isinstance(form_data, dict) else {}

Review Comment:
   Left out on purpose. `get_chart_info` describes a chart through its 
`form_data`, and that is the part of the permalink that replaces the saved 
chart's. `urlParams` are extra query-string parameters Explore puts back on its 
URL, and `chartState` is viz-specific interactive state (for example table 
sorting) that the frontend turns into `ownState` through `chartStateConverter`. 
Neither is part of the chart definition. If a caller turns out to need them, 
they can be exposed as separate fields in a follow-up.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to