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

   ### SUMMARY
   
   Lets an extension contribute a top-level page — a screen of its own, at its 
own URL — as opposed to a view occupying a slot in a screen Superset already 
ships.
   
   An extension declares the page in its manifest and registers a component for 
it at runtime:
   
   ```json
   "contributions": {
     "pages": [
       { "path": "/review-queue", "name": "Review queue",
         "nav": { "label": "Review queue" }, "permission": "can_review" }
     ]
   }
   ```
   
   ```ts
   pages.registerPage({ path: '/review-queue', name: 'Review queue' }, 
ReviewQueue);
   ```
   
   Both halves are needed: the manifest declaration is what the server reads to 
register a URL rule, so a cold load resolves; the runtime registration supplies 
the component. Registering a component for a path the manifest does not declare 
is refused, so the two cannot drift.
   
   #### Paths are checked, not namespaced
   
   The path is the author's to choose, but not theirs to take. Before 
registering a rule, the server checks the path against the routes the 
deployment actually serves. Two checks, because neither subsumes the other:
   
   - **Matching the live URL map.** A literal comparison against known routes 
gets this wrong: `/oauth-authorized/<provider>` never string-matches the 
request it would answer, so a page could take an OAuth callback. Asking the 
router instead catches it, along with redirects (`strict_slashes`) and rules 
answering only other methods.
   - **Scanning rule templates for the same prefix.** A page owns everything 
beneath its path, so a page at `/report` would shadow a core `/report/list` 
through its sub-path rule. No single probe URL is guaranteed to hit a static 
descendant, so the templates are read directly.
   
   Registration runs after operator blueprints, the FAB auth views and every 
core view, so the map it checks is complete. **It re-runs on every boot**, 
which is the part that holds over time: routes Superset registers conditionally 
— the OAuth callback exists only under `AUTH_TYPE = AUTH_OAUTH` — are absent 
where the feature is off, so a page may legitimately take that path there. 
Enabling the feature later puts the real rule in the map first, and the next 
boot refuses the page loudly rather than leaving an extension in front of the 
callback.
   
   A refused page is skipped, not fatal. One extension claiming a taken path 
should not stop the server starting.
   
   Two further layers back this up. The client route is a catch-all pushed 
last, so `<Switch>` gives every host screen precedence the same way Werkzeug 
gives its static rules precedence — pinned by a test that derives host paths 
from the route table, so a route added in the wrong place is covered the day it 
is added. And the extensions API reports only pages that actually got a rule, 
read back from the URL map, so a refused page cannot reach the menu or accept a 
component.
   
   #### Permissions
   
   A page may name a permission. The host creates it against the extension when 
the extension loads — not during `superset init`, because an extension is 
installed long after a deployment is initialised and an administrator cannot 
grant a permission that does not exist. It reads as `can review on acme.tools` 
in the Roles screen, so an extension's permissions group together and two 
extensions naming the same permission stay separate.
   
   Omitting the field leaves the page open to any signed-in user, which is what 
every page did before this field existed.
   
   A user without the permission gets 403 rather than FAB's `has_access` 
redirect to the login page — they are already signed in, so sending them to log 
in again says nothing true. Login still comes first, so an anonymous visitor is 
redirected rather than refused; 403 would disclose that the page exists to 
someone who has not identified themselves.
   
   The Admin role is given these permissions on creation, and the grant is 
re-checked each boot so a role that lost it repairs itself. This is not a new 
policy: `sync_role_definitions` builds Admin from every permission that is not 
one of the four object-level ones, so `superset init` would grant it anyway — 
just not until someone next ran init, which installing an extension does not 
otherwise require. Correspondingly, a page naming one of `database_access`, 
`catalog_access`, `schema_access` or `datasource_access` is refused, since 
those are exactly what Superset excludes when deciding Admin, and such a page 
would have its grant stripped at the next `superset init` with nothing in the 
logs to explain it.
   
   #### Also included
   
   `useSubPath()` and `useNavigate()`, so a page can own sub-paths 
(`/review-queue/42/edit`) and navigate within itself as a client-side 
transition. These exist because `react-router-dom` is not in the Module 
Federation share scope — an extension importing it gets a second module 
instance whose context the host's `<Router>` never populates. 
`@superset-ui/core` **is** added to the share scope, so a page gets 
`SupersetClient` with working CSRF rather than hand-rolling fetches.
   
   #### Open question for reviewers
   
   Paths are flat and author-chosen. The alternative is a host-derived 
namespace (`/extensions/<publisher>.<name>/<slug>`), which makes collisions 
impossible by construction rather than by inspection, at the cost of long URLs. 
Grafana (`/a/<plugin-id>/*`) and Mattermost (`/plug/<id>`) namespace; Kibana 
(`/app/<id>`) does not.
   
   Flat paths are deliberate here, but they carry three costs worth naming:
   
   1. **Upgrade fragility.** If Superset later ships a route at a path an 
extension holds, that page is refused at the next boot — loud, but broken.
   2. **Install order decides collisions between extensions.** Detected and 
logged, but the winner depends on config order.
   3. **No attribution in the URL.** `/approvals` reads as part of Superset, 
with nothing saying third-party code serves it.
   
   Happy to move to a namespaced prefix if reviewers prefer it — the shape is 
set in `pagePath` and the manifest's path pattern.
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   
   After: a contributed page at `/approvals` with its navigation entry, 
alongside Dashboards / Charts / Datasets. A page whose permission the user 
lacks is absent from the navigation bar and answers 403 on a direct load.
   
   ### TESTING INSTRUCTIONS
   
   Automated:
   
   ```bash
   pytest tests/unit_tests/extensions/ -q
   cd superset-frontend && npx jest src/core/pages src/views/routes.test.tsx 
src/extensions
   ```
   
   Manually, with `ENABLE_EXTENSIONS` on and an extension declaring two pages 
(one with a `permission`):
   
   1. Load the page's path directly — it renders inside Superset's chrome.
   2. Load a deep path beneath it (`/review-queue/42/edit`) — it renders, and 
`useSubPath()` reports `/42/edit`.
   3. Click the navigation entry — a client-side transition, not a full page 
load.
   4. Add a page declaring a path Superset already serves, e.g. `/welcome`. On 
restart the log names the conflict, the page is not registered, and `/welcome` 
still serves Superset's own screen.
   5. As a user without the declared permission, the second page is absent from 
the navigation bar and a direct load answers 403. Grant the permission on the 
extension in the Roles screen and both work.
   6. Revoke the permission from Admin and restart — it is granted again, and 
the log says so.
   
   ### ADDITIONAL INFORMATION
   
   - [ ] Has associated issue:
   - [x] Required feature flags: `ENABLE_EXTENSIONS`
   - [x] Changes UI
   - [ ] Includes DB Migration (follow approval process in 
[SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [x] Introduces new feature or API
   - [ ] Removes existing feature or API
   
   No migration: the permissions these pages use are created through the 
security manager when an extension loads, as FAB permissions are elsewhere.
   


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