hunter3x3-tech opened a new issue, #44385:
URL: https://github.com/apache/superset/issues/44385

   ### Bug description
   
   # DELETE leaves stale contextual cache mappings, causing deleted 
filter/form-data keys to be reused
   
   ## Bug description
   
   The temporary-state DELETE paths for dashboard filter state and Explore form 
data remove the primary cache entry, but can leave the secondary contextual 
mapping pointing to the deleted key.
   
   As a result, a subsequent POST from the same session/tab/context can reuse a 
key that was previously deleted.
   
   This affects both:
   
   * `DashboardFilterStateRestApi`
   * `ExploreFormDataRestApi`
   
   The behavior is present on current `master` and the same underlying logic is 
present in 6.1.0.
   
   ## Root cause
   
   Temporary state creation keeps a secondary mapping so the same 
session/tab/resource can reuse an existing generated key.
   
   For dashboard filter state, the mapping is based on:
   
   ```python
   contextual_key = cache_key(
       session.get("_id"),
       tab_id,
       resource_id,
   )
   ```
   
   The DELETE command attempts to remove the same mapping:
   
   ```python
   tab_id = cmd_params.tab_id
   contextual_key = cache_key(
       session.get("_id"),
       tab_id,
       resource_id,
   )
   cache_manager.filter_state_cache.delete(contextual_key)
   ```
   
   However, the DELETE API creates the command parameters without a `tab_id`.
   
   Effectively, the command receives:
   
   ```python
   cmd_params.tab_id is None
   ```
   
   so it attempts to delete a mapping for:
   
   ```text
   <session>;None;<resource>
   ```
   
   rather than the mapping originally created for, for example:
   
   ```text
   <session>;1;<resource>
   ```
   
   The primary state entry is deleted successfully, so the DELETE request 
returns success, while the actual contextual mapping remains.
   
   `ExploreFormDataRestApi` has the same mismatch: `DeleteFormDataCommand` uses 
`cmd_params.tab_id` to construct the contextual key, but the DELETE endpoint 
passes only `key`.
   
   ## Why this matters
   
   On the next POST from the same context, the create path reads the stale 
contextual mapping:
   
   ```python
   key = cache_manager.filter_state_cache.get(contextual_key)
   
   if not key or not tab_id:
       key = random_key()
   ```
   
   Because the stale mapping still returns the previously deleted key and 
`tab_id` is present, no new random key is generated.
   
   The deleted key is then used again for the newly created state.
   
   The same reuse pattern exists in `CreateFormDataCommand`.
   
   ## Reproduction
   
   Using a dashboard with filter-state access:
   
   1. Create filter state with a tab id:
   
   ```http
   POST /api/v1/dashboard/<dashboard_id>/filter_state?tab_id=1
   ```
   
   with a valid JSON-string value representing `State A`.
   
   Record the returned key as `K1`.
   
   2. Delete it:
   
   ```http
   DELETE /api/v1/dashboard/<dashboard_id>/filter_state/K1
   ```
   
   The request returns success.
   
   3. Verify the primary entry is gone:
   
   ```http
   GET /api/v1/dashboard/<dashboard_id>/filter_state/K1
   ```
   
   This should return 404.
   
   4. Create another filter state using the same browser session, dashboard and 
`tab_id=1`, this time with `State B`:
   
   ```http
   POST /api/v1/dashboard/<dashboard_id>/filter_state?tab_id=1
   ```
   
   ### Actual behavior
   
   The second POST can return:
   
   ```text
   K1
   ```
   
   again.
   
   The stale contextual mapping still points to the previously deleted key, so 
the create path reuses it and recreates the primary state entry under `K1`.
   
   A subsequent GET of `K1` can therefore return `State B`.
   
   ### Expected behavior
   
   A successful DELETE should invalidate both the primary entry and its 
contextual mapping.
   
   After deletion, creating state again in the same context should generate a 
fresh key:
   
   ```text
   K2 != K1
   ```
   
   ## Affected code
   
   Dashboard filter state:
   
   ```text
   superset/commands/dashboard/filter_state/create.py
   superset/commands/dashboard/filter_state/delete.py
   superset/temporary_cache/api.py
   ```
   
   Explore form data:
   
   ```text
   superset/commands/explore/form_data/create.py
   superset/commands/explore/form_data/delete.py
   superset/explore/form_data/api.py
   ```
   
   ## Existing test gap
   
   Current tests verify that DELETE returns successfully, but do not verify 
that the contextual mapping is removed.
   
   A regression test could exercise:
   
   ```text
   create with tab_id=1
   → delete returned key
   → create again with the same context
   → assert second key != deleted key
   ```
   
   The existing Explore command test is especially close to this scenario: it 
creates form data with `tab_id=1`, invokes `DeleteFormDataCommand` without a 
`tab_id`, and only asserts that deletion returns `True`.
   
   ## Historical intent
   
   PR #18576 introduced the contextual secondary mapping used for key reuse.
   
   Its design explicitly states that the secondary mapping should be kept in 
sync with DELETE events.
   
   The current API/command contract appears to prevent that synchronization 
because the DELETE routes do not supply the context required by their delete 
commands.
   
   ## Suggested remediation direction
   
   The DELETE path needs a reliable way to identify and remove the contextual 
mapping associated with the primary key.
   
   Simply deleting the primary cache entry is insufficient because the 
surviving mapping can cause that key to be reused later.
   
   Possible approaches include passing and preserving the necessary context on 
DELETE, or maintaining a reverse association that allows the contextual mapping 
to be invalidated from the primary key.
   
   ## Environment
   
   * Superset: current `master`
   * Also present in the corresponding temporary-cache logic in Superset 6.1.0
   * Python: Not applicable for source-level analysis
   * Node: Not applicable
   * Browser: Not applicable
   
   
   ### Screenshots/recordings
   
   _No response_
   
   ### Superset version
   
   6.1.0
   
   ### Python version
   
   I don't know
   
   ### Node version
   
   I don't know
   
   ### Browser
   
   Chrome
   
   ### Additional context
   
   _No response_
   
   ### Checklist
   
   - [x] I have searched Superset docs and Slack and didn't find a solution to 
my problem.
   - [x] I have searched the GitHub issue tracker and didn't find a similar bug 
report.
   - [x] I have checked Superset's logs for errors and if I found a relevant 
Python stacktrace, I included it here as text in the "additional context" 
section.


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