dmunozv04 opened a new issue, #45032: URL: https://github.com/apache/superset/issues/45032
*Please make sure you are familiar with the SIP process documented* [here](https://github.com/apache/superset/issues/5602). The SIP will be numbered by a committer upon acceptance. ## [SIP] Proposal for role import and export ### Motivation Superset instance administrators may need to move roles between instances, such as from development to staging or production. It can also be useful to export roles for documentation purposes. This has been mentioned before in [this GitHub discussion](https://github.com/apache/superset/discussions/27238) but it was never implemented. Flask-AppBuilder already supports this via CLI commands (using the `export-roles` and `import-roles` commands). ### Proposed Change This feature would add a new option in the Security -> List Roles page to allow administrator users without CLI access to import/export roles. #### Screenshots <img width="919" height="845" alt="Image" src="https://github.com/user-attachments/assets/6894bfa3-6c12-4b51-9640-037cf694e7df" /> <img width="919" height="786" alt="Image" src="https://github.com/user-attachments/assets/9a593a17-81ed-4983-ac52-4e793e97ebfc" /> <img width="919" height="845" alt="Image" src="https://github.com/user-attachments/assets/b3f53424-7121-4950-8302-3453f372cd73" /> Superset has two different types of role permissions: - Application permissions, which can be identified by the fab permission and view_menu; such as `can_read` on `Dashboard`. - Data permissions, which refer to database or datasource (dataset) access. Their view_menu names are fragile since they can easily be changed, so ideally their uuids would be used. Since built-in roles are managed by Superset, they shouldn't be affected by this feature. This SIP proposes two API design options: #### Option 1: FAB-like JSON endpoints This option would reuse the role structure that Flask-AppBuilder supports via its CLI. Permissions are represented by a `(permission.name, view_menu.name)` pair. ```http GET /api/v1/security/roles/export/?q=<Rison-encoded-role-ids> ``` The response would be a JSON array of the selected roles: ```json [ { "name": "FinanceReader", "permissions": [ { "permission": { "name": "can_read" }, "view_menu": { "name": "Dashboard" } }, { "permission": { "name": "database_access" }, "view_menu": { "name": "[<database-name>].(id:<database-id>)" } }, { "permission": { "name": "catalog_access" }, "view_menu": { "name": "[<database-name>].[finance]" } }, { "permission": { "name": "schema_access" }, "view_menu": { "name": "[<database-name>].[finance].[reporting]" } }, { "permission": { "name": "datasource_access" }, "view_menu": { "name": "[<database-name>].[<dataset-name>](id:<dataset-id>)" } }, { "permission": { "name": "datasource_access" }, "view_menu": { "name": "[<semantic-layer-name>](id:<semantic-layer-uuid-without-hyphens>)" } }, { "permission": { "name": "datasource_access" }, "view_menu": { "name": "[<semantic-layer-name>].[<semantic-view-name>](id:<semantic-view-id>)" } } ] } ] ``` The UI would turn this response into a JSON file download. Import would also accept the same JSON payload. ```http POST /api/v1/security/roles/import/ Content-Type: application/json ``` The main advantage of this format is its compatibility with FAB's CLI. The main drawback is the datasource names could change and break exports/imports. #### Option 2: YAML role definitions with UUID references This option would export each role as a YAML file. Exports would return a ZIP, just like the existing dataset exports. The format would look like this: ```text roles_export_<timestamp>.zip ├── metadata.yaml └── roles/ ├── analytics_viewer.yaml └── finance_reader.yaml ``` The `metadata.yaml` would have a similar structure as the existing exports: ```yaml version: 1.0.0 type: Role timestamp: '<timestamp>' ``` A role document could have this structure: Resource type can be a SQL dataset, a semantic layer or semantic view. Optional resource labels make the exported document human-readable, and are ignored by imports. ```yaml version: 1.0.0 type: role role: name: FinanceReader permissions: # FAB permission: a permission and a view menu. - permission: can_read view_menu: Dashboard # Access to one database. - permission: database_access resource: type: database uuid: <database-uuid> label: Finance warehouse # Display only # Catalog and schema names are part of the permission identity. - permission: catalog_access resource: type: catalog database_uuid: <database-uuid> database_label: Finance warehouse # Display only name: finance - permission: schema_access resource: type: schema database_uuid: <database-uuid> database_label: Finance warehouse # Display only catalog: finance name: reporting # datasource_access can target different objects. - permission: datasource_access resource: type: dataset uuid: <dataset-uuid> label: Monthly revenue # Display only database_label: Finance warehouse # Display only; - permission: datasource_access resource: type: semantic_layer uuid: <semantic-layer-uuid> label: Finance metrics # Display only - permission: datasource_access resource: type: semantic_view uuid: <semantic-view-uuid> label: Revenue by month # Display only ``` On import, Superset would resolve the UUID to the destination object, or fail if the object doesn't exist. This is the main advantage of this option, being resistant to changes such as renaming. The import endpoint would accept a ZIP archive, expecting the format explained above. ```http GET /api/v1/security/roles/export/?q=<Rison-encoded-role-ids> Accept: application/zip POST /api/v1/security/roles/import/ Content-Type: application/zip ``` Another advantage of this format is that it supports versioning, if needed in the future. Option 2 seems like the better choice, but I wanted to leave the choice in case maintainers felt the other option was better. A reference PR is also provided Implementing option 1, used to create the screenshots above: #45031 #### Import conflict handling Regardless of the option selected, the proposal shoud decide how conflicts could be handled. An additive import option would create missing roles and also add permissions to existing roles, while retaining permissions that aren't in the import. An overwrite option, with a warning similar to the one used for dataset imports, would be authoritative and remove permissions absent from the import. ### New or Changed Public Interfaces #### REST API Both options add administrator protected endpoints for exporting and importing role definitions. The API interface would be different depending on the option selected, but the endpoints would be the same: | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/security/roles/export/?q=<Rison-encoded-role-ids>` | Export selected roles to JSON/ZIP archive | | `POST` | `/api/v1/security/roles/import/` | Import roles from JSON/ZIP archive | #### UI New buttons would also be added in the List Roles interface to export a single role, export multiple selected roles and import roles. ### New dependencies No new dependencies are expected. ### Migration Plan and Compatibility No database migrations are expected. No existing features are expected to change. ### Rejected Alternatives - Using the existing `superset fab export-roles` and `superset fab import-roles` CLI commands, since they're only accessible for administrators with console access. - Wiring the FAB export/import logic to the API endpoints, since the FAB functions accept file paths and would complicate request handling. -- 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]
