mikebridge opened a new pull request, #43628:
URL: https://github.com/apache/superset/pull/43628

   ### SUMMARY
   
   `POST 
/api/v1/{chart,dashboard,dataset}/<uuid>/versions/<version_uuid>/restore` never 
checked
   `is_managed_externally`. The dashboard and chart UIs hide the restore 
control for externally managed
   entities, but any direct caller with edit rights — a script, a notebook, an 
integration — could restore an
   older version over a dbt-synced dataset or an externally provisioned 
dashboard. The next sync then either
   overwrites the restore or resurrects content the external owner had removed. 
The published documentation
   (`docs/using-superset/version-history.mdx`) already states that restore "is 
withheld from externally managed
   entities"; this PR makes that statement true.
   
   **The change** is one gate and one mapping:
   
   - `BaseRestoreVersionCommand.validate()` gains a fourth check, after the 
editorship check, raising a new
     shared `ExternallyManagedRestoreError` (a `ForbiddenError` subclass). 
Because the three per-entity restore
     commands are pure declarations over that base, one edit covers charts, 
dashboards, and datasets identically.
   - `restore_version_endpoint()` maps it to `403 {"message": "Version restore 
is unavailable for externally
     managed entities."}` via `api.response(403, message=…)`. FAB's 
`response_403()` always emits
     `{"message": "Forbidden"}`, so a caller can distinguish a policy refusal 
from a permission denial by the body.
   
   **Design notes reviewers will want:**
   
   - **Ordering.** The gate runs *after* `raise_for_editorship`, so a caller 
with no rights to the entity still
     gets the plain permission denial and is never told the entity is 
externally managed. It runs *before* version
     resolution, so a refused restore writes nothing — no version row, no 
partial update.
   - **This is the first server-side use of `is_managed_externally`.** No 
update or delete command enforces the
     flag today; those operations remain gated in the browser alone. After this 
PR a permitted editor is refused a
     version restore but can still submit an equivalent direct `PUT`. That 
asymmetry is accepted deliberately: the
     documentation promises the restriction for restore and for nothing else, 
and widening enforcement to
     update/delete is a product decision for the Versioning epic rather than 
this correctness fix. Follow-up:
     _to be filed before this leaves draft_.
   - **Soft-delete recovery is deliberately not gated.** Recovering an archived 
externally managed entity changes
     visibility, not content, and is a useful escape hatch when a synced asset 
is archived by mistake. The
     asymmetry is pinned by a test on one and the same entity: archive → 
recover succeeds → version restore
     refused.
   - **No `UPDATING.md` entry.** A released endpoint does change from 200 to 
403 for one class of caller, but the
     change brings the API into line with already-published documentation and 
is a correctness fix, not a
     boundary change.
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   
   Not applicable — no UI change. Before/after is the API response below.
   
   ### TESTING INSTRUCTIONS
   
   Automated (all control-first — every behavioural test was run red against 
the pre-fix code first):
   
   ```bash
   pytest tests/unit_tests/versioning/                                    # 140 
passed (14 new)
   pytest tests/integration_tests/charts/version_restore_tests.py \
          tests/integration_tests/dashboards/version_restore_tests.py \
          tests/integration_tests/datasets/version_restore_tests.py     # 37 
passed (4 new)
   ```
   
   Manual:
   
   1. Mark a chart externally managed (`UPDATE slices SET is_managed_externally 
= true WHERE uuid = '<uuid>'`).
   2. As a user who can edit it, `POST 
/api/v1/chart/<uuid>/versions/<version_uuid>/restore`.
      - **Before:** `200 {"message": "OK"}`; the chart is rewritten and — 
because the flag is itself a versioned
        column — restoring an older version also silently flips 
`is_managed_externally` back to `false`.
      - **After:** `403 {"message": "Version restore is unavailable for 
externally managed entities."}`; chart and
        version history unchanged.
   3. As a user who *cannot* edit it: `403 {"message": "Forbidden"}` — 
unchanged, and the body differs from step 2.
   4. `GET …/versions/`, `GET …/versions/<v>/`, `GET …/activity/` all still 
`200` for the managed chart.
   5. `DELETE /api/v1/chart/<id>` then `POST /api/v1/chart/<uuid>/restore` 
(recovery): both `200`; a version
      restore immediately after is still `403`.
   
   ### ADDITIONAL INFORMATION
   
   - [x] Has associated issue: sc-115616 (origin: scope note in the 
verification review of #41551)
   - [ ] Required feature flags: none new (`VERSION_HISTORY` / 
`ENABLE_VERSIONING_CAPTURE`, already on by default)
   - [ ] Changes UI
   - [ ] Includes DB Migration
   - [ ] Introduces new feature or API
   - [ ] Removes existing feature or API
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


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