mirror of
https://github.com/apache/superset.git
synced 2026-07-12 17:55:38 +00:00
Closes the 404 gap for legacy `/superset/<canonical>` bookmarks after `Superset.route_base = ""` collapsed the historical `/superset` prefix off every view. Adds an outermost WSGI shim that 308-redirects enumerated legacy paths to their canonical equivalents (or 410s POSTs against GET-only canonicals, where a 308 would have the client retry-POST into a 405). Layering invariant ------------------ The shim wraps `app.wsgi_app` in `create_app()` *after* `init_app()` returns, so it sits outside AppRootMiddleware / ProxyFix / ChunkedEncodingFix / ADDITIONAL_MIDDLEWARE and sees the raw inbound `PATH_INFO`. `Location` is built from the `app_root` captured at construction time + the canonical path — never from `environ["SCRIPT_NAME"]` (unset at this outer layer) and never from `X-Forwarded-*`. See module docstring + PLAN.md "WSGI layering invariant" for the full ordering rationale. AF-4 coupling closure --------------------- `SqlaTable.sql_url` previously used naive `?table_name=…` concatenation, which produced double-`?` URLs when `Database.sql_url` already carried a query string (Database.sql_url was reshaped in Slice 3 to `/sqllab/?dbid=<id>`). Switch to `urlsplit` / `parse_qsl` / `urlencode` with `quote_via=quote` so query strings compose correctly and names with special characters (`/`, `&`, `+`, …) are properly encoded. Closed-set discipline --------------------- `LEGACY_REDIRECT_MAP` is a 16-row closed table, each row verified against the canonical endpoint's `@expose` decorator at HEAD. `/superset/sql/<id>/` is intentionally absent — `Database.sql_url` changed shape (path → query string) so no 1:1 mapping exists; the hard re-bookmark break is documented in UPDATING.md. A snapshot regression test pins the keyset. Oracle results (all RED-against-HEAD, reverted) ----------------------------------------------- 5-a remove shim wrap-site in app.py → 17 GETs fail 5-b flip `path_info.startswith` → exact-only → tail-bearing rows red 5-c drop allowed-methods gate → POST-410 rows red 5-d emit 302 instead of 308 → status assertion red 5-e read SCRIPT_NAME instead of captured app_root → contamination guard red 5-f read X-Forwarded-Prefix → contamination guard red 5-g `_LEGACY_PREFIX = ""` → pass-through guard red 5-h drop query-string passthrough → QS preservation red 5-i swap exact/longest-prefix order in _match → /dashboard/p/ row red 5-j AF-4: revert sql_url to naive concat → double-`?` test red Round-3/4/6 review closures --------------------------- * round-3 [High] prefix-source closure: `app_root_prefix` captured once at construction; never re-read from environ. * round-4 [High] wrap-site closure: shim installed at the single sanctioned outermost wrap site in `create_app()`; pinned by `test_wrap_order_*`. * round-6 `quote(safe=…)` pin: `Location` is %-encoded with a safe set that preserves header-safe URL bytes while sanitising raw control bytes from `PATH_INFO`. * round-6 outermost-only-within-`create_app()` invariant: unconditional wrap (independent of `app_root != "/"`) — legacy bookmarks exist under root deployments too. * H2 closure: 410 (not 308) for POST-against-GET-only avoids the 308→retry → 405 trap. Tests ----- Test file lives at `tests/unit_tests/middleware/` (not `integration_tests/` per PLAN) because the local docker-light env can't service `/login/` POST. Coverage is preserved via `werkzeug.test.Client` driving the middleware around a sentinel inner WSGI app, plus `create_app()` + patched `init_app()` for wrap-order assertions, plus live-route shadow pins. * `tests/unit_tests/middleware/test_legacy_prefix_redirect.py` — 72 tests (path rewriter, query-string preservation, SCRIPT_NAME/X-Forwarded contamination guards, closed-set snapshot, wrap-order, live-route shadow pins, AF-4 query encoding). * `tests/unit_tests/initialization_test.py` — `_unwrap_to_app_root` helper threads the new outermost layer so existing `TestCreateAppRoot` cases keep passing. UPDATING.md ----------- Documents (a) legacy `/superset/*` 308 shim with EOL at 5.0.0, (b) hard re-bookmark break for `/superset/sql/<database_id>/`, (c) `SqlaTable.sql_url` query-string format change (AF-4 fix). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
17 lines
785 B
Python
17 lines
785 B
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|