You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/06/27 09:18:31 UTC

(superset) branch fix/followup-41397-follow-up-from-41397-fix-async-derive-as created (now 7400c8f1d1b)

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

rusackas pushed a change to branch fix/followup-41397-follow-up-from-41397-fix-async-derive-as
in repository https://gitbox.apache.org/repos/asf/superset.git


      at 7400c8f1d1b fix(async): include datasets and rev claims in guest channel HMAC

This branch includes the following new commits:

     new 7400c8f1d1b fix(async): include datasets and rev claims in guest channel HMAC

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(superset) 01/01: fix(async): include datasets and rev claims in guest channel HMAC

Posted by ru...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch fix/followup-41397-follow-up-from-41397-fix-async-derive-as
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 7400c8f1d1b6402a18a13e5ae7a70a49dc2622e4
Author: Evan <ev...@preset.io>
AuthorDate: Sat Jun 27 02:17:59 2026 -0700

    fix(async): include datasets and rev claims in guest channel HMAC
    
    Follow-up to #41397. The HMAC-SHA256 message used to derive the embedded
    async channel id only covered user, resources, iat, exp and aud. The
    optional `datasets` and `rev` guest-token claims were left out, so two
    tokens that differ only in their dataset allowlist or revocation version
    would collide on the same async channel. Fold both into the message so
    those scopes derive distinct channels.
    
    Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
 superset/async_events/async_query_manager.py       |  5 +++
 .../async_events/async_query_manager_tests.py      | 38 ++++++++++++++++++++++
 2 files changed, 43 insertions(+)

diff --git a/superset/async_events/async_query_manager.py b/superset/async_events/async_query_manager.py
index a3ea0b58333..050532fad68 100644
--- a/superset/async_events/async_query_manager.py
+++ b/superset/async_events/async_query_manager.py
@@ -245,6 +245,11 @@ class AsyncQueryManager:
                 "iat": token.get("iat"),
                 "exp": token.get("exp"),
                 "aud": token.get("aud"),
+                # ``datasets`` and ``rev`` are optional scope claims, so tokens
+                # that differ only in their dataset allowlist or revocation
+                # version still derive distinct channels.
+                "datasets": token.get("datasets"),
+                "rev": token.get("rev"),
             },
             sort_keys=True,
         ).encode("utf-8")
diff --git a/tests/unit_tests/async_events/async_query_manager_tests.py b/tests/unit_tests/async_events/async_query_manager_tests.py
index bd7f8f8b1a2..79acb17e5db 100644
--- a/tests/unit_tests/async_events/async_query_manager_tests.py
+++ b/tests/unit_tests/async_events/async_query_manager_tests.py
@@ -222,6 +222,44 @@ def test_parse_channel_id_from_request_as_guest_user_differs_per_token(
     assert first != second
 
 
+@mock.patch("superset.is_feature_enabled")
+def test_parse_channel_id_from_request_as_guest_user_differs_per_scope(
+    is_feature_enabled_mock, async_query_manager
+):
+    """
+    Tokens that differ only in the optional ``datasets`` allowlist or ``rev``
+    revocation version must still derive distinct channel ids, otherwise two
+    differently scoped embedded sessions would collide on the same stream.
+    """
+    is_feature_enabled_mock.return_value = True
+
+    base_token = {
+        "user": {},
+        "resources": [{"type": "dashboard", "id": "some-uuid"}],
+        "rls_rules": [{"clause": '"STATEID" = 3'}],
+        "iat": 1700000000.0,
+        "exp": 1700000300.0,
+        "aud": "http://0.0.0.0:8080/",
+        "type": "guest",
+    }
+
+    request = Mock()
+    request.cookies = {}
+
+    g.user = security_manager.get_guest_user_from_token(dict(base_token))
+    baseline = async_query_manager.parse_channel_id_from_request(request)
+
+    g.user = security_manager.get_guest_user_from_token({**base_token, "datasets": [1]})
+    with_datasets = async_query_manager.parse_channel_id_from_request(request)
+
+    g.user = security_manager.get_guest_user_from_token({**base_token, "rev": 1})
+    with_rev = async_query_manager.parse_channel_id_from_request(request)
+
+    assert baseline != with_datasets
+    assert baseline != with_rev
+    assert with_datasets != with_rev
+
+
 @mark.parametrize(
     "cache_type, cache_backend",
     [