sadpandajoe commented on code in PR #45068:
URL: https://github.com/apache/superset/pull/45068#discussion_r4450789659


##########
superset/charts/api.py:
##########
@@ -1762,6 +1769,7 @@ def import_(self) -> Response:
         ssh_tunnel_priv_key_passwords = parse_json_form_field(
             "ssh_tunnel_private_key_passwords"
         )
+        encrypted_extra_secrets = 
parse_json_form_field("encrypted_extra_secrets")

Review Comment:
   `encrypted_extra_secrets` is only checked to be a JSON object at the top 
level, but `load_configs()` calls `.items()` on each per-file value. A request 
carrying `encrypted_extra_secrets={"databases/db.yaml": "secret"}` for a bundle 
with a masked Secure Extra parses fine and then raises an `AttributeError`, so 
the client gets an HTTP 500 instead of a 400/422. The same unchecked parse is 
repeated in the dashboard, dataset and saved-query import routes. Should the 
per-file values be validated as `dict[str, str]` before the command runs?



##########
tests/unit_tests/charts/commands/data/test_get_data_command.py:
##########
@@ -445,3 +445,28 @@ def 
test_get_query_handles_parsing_error_with_null_sql_value() -> None:
         assert "query" not in result
         assert result["error"] == "Parsing error occurred"
         assert result["language"] == "sql"
+
+
+def test_database_error_while_building_the_query_is_a_failed_query() -> None:
+    """
+    A connection failure while the query is being built -- a virtual dataset's
+    default-schema lookup connects -- is reported as a failed query (a 400),
+    not left to escape as an unexpected server error (an HTML 500).
+    """
+    from sqlalchemy.exc import OperationalError
+
+    mock_query_context = Mock(spec=QueryContext)
+    mock_query_context.result_type = ChartDataResultType.FULL
+    mock_query_context.get_payload.side_effect = OperationalError(
+        "connect", {}, Exception("Incorrect username or password was 
specified.")
+    )
+    database = Mock()
+    database.db_engine_spec.extract_error_message.return_value = (
+        "Incorrect username or password was specified."
+    )
+    mock_query_context.datasource = Mock(database=database)
+
+    with pytest.raises(ChartDataQueryFailedError) as excinfo:
+        ChartDataCommand(mock_query_context).run()

Review Comment:
   This test goes through `run()`, but the chart data API calls `execute()`, 
which has its own new `except SQLAlchemyError` branch. If that branch were 
removed, this test would still pass and chart requests would go back to 
surfacing the connection failure as an unhandled 500. Could you add a case 
where `get_payload_result` raises `OperationalError` and `execute()` is 
expected to raise `ChartDataQueryFailedError` with the extracted message?



##########
superset/utils/json.py:
##########
@@ -362,6 +367,37 @@ def get_masked_fields(
     return masked
 
 
+def unrevealable_masked_fields(
+    old_payload: dict[str, Any] | None,
+    new_payload: dict[str, Any],
+    sensitive_fields: set[str],
+) -> list[str]:
+    """
+    Returns masked fields in ``new_payload`` that ``old_payload`` cannot 
reveal.
+
+    :func:`reveal_sensitive` swaps a mask back for the stored value at the same
+    path. Where the stored payload has no value there, the mask would be kept 
and
+    stored as if it were the secret, so callers that must not do that (imports
+    overwriting a connection) use this to ask for the real value instead. A
+    stored mask counts as no value: earlier releases saved one when an import
+    had nothing to reveal, and revealing it only hands the mask back.
+
+    :param old_payload: The stored payload, if any
+    :param new_payload: The incoming payload, possibly containing masks
+    :param sensitive_fields: The set of fields to check, as JSONPath 
expressions
+    :returns: JSONPath expressions for masked fields with nothing to reveal
+    """
+    return [
+        field
+        for field in get_masked_fields(new_payload, sensitive_fields)
+        if old_payload is None
+        or not any(
+            match.value and match.value != PASSWORD_MASK

Review Comment:
   `match.value and ...` treats a stored `null` as no value. Before this PR, 
`redact_sensitive` masked `null` fields too, so an older Snowflake export of an 
unencrypted-key connection carries `auth_params.privatekey_pass` as the mask, 
and `reveal_sensitive` used to restore the stored `null` from it. Now, 
overwriting that connection from the same archived export lists the passphrase 
as unrevealable, and the import dialog requires a non-empty answer for it, so 
the import cannot be completed without inventing a passphrase. Should a stored 
`None` count as revealable here, with only a stored mask treated as missing?



##########
docs/docs/using-superset/embedding.mdx:
##########
@@ -214,6 +321,7 @@ One caveat when the host page lives on a different site 
than Superset: the defau
 - **Row-level security** — pass `rls` rules in the guest token request to 
restrict which rows are visible to the embedded user. Semantic views cannot 
enforce guest-token SQL clauses, so queries and cached-result reads are 
rejected when a global rule or a rule scoped to the semantic view applies. For 
hosts, a global guest rule deliberately refuses every semantic view, and a 
dataset-scoped rule can also refuse a semantic view even when the rule was 
intended for a SQL dataset. Read permissions do not override these 
restrictions. Semantic views remain accessible when no guest rule applies; SQL 
datasets continue to enforce their applicable rules.
 - **Allowed domains** — restrict which host origins can embed a dashboard by 
setting **Allowed Domains** per-dashboard in the _Embed_ settings modal. 
Superset checks the request's `Referer` header against this list before serving 
the embedded view; an empty list allows any origin, so configure this 
explicitly for production.
 - **Redacted errors** — API responses to a guest token report a generic `An 
error occurred while fetching the data.` instead of the underlying error, since 
engine errors quote catalog, schema, table and column names. Errors Superset 
raises itself — access denials, timeouts, payload validation — keep their 
message, and the full error is always available in the server logs.
+- **OAuth2 connections and guests** — a guest cannot complete an OAuth2 
authorization, so Superset does not start one for them, and an embedded viewer 
on an OAuth2 connection with no applicable credential is never sent into a 
login flow they cannot finish. With `EMBEDDED_CREDENTIAL_FALLBACK` off, the 
connection attempt fails with the database driver's authentication error. With 
it on, Superset refuses the query before connecting. Either way the viewer sees 
the generic `An error occurred while fetching the data.` message, and the 
reason is in the server logs. To serve embedded dashboards from such a 
connection, see [`EMBEDDED_CREDENTIAL_FALLBACK`](#embedded_credential_fallback).

Review Comment:
   "With it on, Superset refuses the query before connecting" reads as true for 
every OAuth2 connection, but the refusal only applies when the engine opts in 
(Snowflake) and impersonation is on. An operator enabling the flag for, say, a 
Trino OAuth2 connection will still go through the ordinary connection path and 
not get the early refusal this describes. Can this sentence name those two 
conditions?



-- 
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