From 23753b3242faa9fa195944c47747d93964167d5d Mon Sep 17 00:00:00 2001 From: baha-bouali Date: Fri, 28 Aug 2026 17:49:46 +0100 Subject: [PATCH 1/4] Generate the FAB REST API permission table from source --- .pre-commit-config.yaml | 15 + .../auth-manager/_api_permissions_table.rst | 557 ++++++++++++++++++ .../fab/docs/auth-manager/access-control.rst | 72 +-- scripts/ci/prek/fab_permissions_doc.py | 353 +++++++++++ 4 files changed, 931 insertions(+), 66 deletions(-) create mode 100644 providers/fab/docs/auth-manager/_api_permissions_table.rst create mode 100755 scripts/ci/prek/fab_permissions_doc.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5f575d25296c4..09b078adcbfcd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -729,6 +729,21 @@ repos: ^scripts/ci/prek/extract_permissions\.py$| ^airflow-core/docs/security/api_permissions_ref\.rst$ pass_filenames: false + - id: generate-fab-permissions-doc + name: Generate FAB auth manager REST API permission table + entry: ./scripts/ci/prek/fab_permissions_doc.py + language: python + files: > + (?x) + ^airflow-core/src/airflow/api_fastapi/core_api/routes/public/.*\.py$| + ^airflow-core/src/airflow/api_fastapi/core_api/security\.py$| + ^providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager\.py$| + ^providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override\.py$| + ^providers/fab/src/airflow/providers/fab/www/security/permissions\.py$| + ^scripts/ci/prek/extract_permissions\.py$| + ^scripts/ci/prek/fab_permissions_doc\.py$| + ^providers/fab/docs/auth-manager/_api_permissions_table\.rst$ + pass_filenames: false - id: update-tested-versions name: Update tested Python/DB/Kubernetes versions in docs entry: ./scripts/ci/prek/update_tested_versions.py diff --git a/providers/fab/docs/auth-manager/_api_permissions_table.rst b/providers/fab/docs/auth-manager/_api_permissions_table.rst new file mode 100644 index 0000000000000..1e1e69c812693 --- /dev/null +++ b/providers/fab/docs/auth-manager/_api_permissions_table.rst @@ -0,0 +1,557 @@ + .. 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. + +.. THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY. + Regenerate with: python scripts/ci/prek/fab_permissions_doc.py + Trigger: prek run generate-fab-permissions-doc --all-files + +.. list-table:: Stable REST API permissions (FAB auth manager) + :header-rows: 1 + :widths: 45 8 32 15 + + * - Endpoint + - Method + - Permissions + - Minimum role + * - ``/api/v2/assets`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets`` + - GET + - Asset Aliases.can_read + - Viewer + * - ``/api/v2/assets/aliases`` + - GET + - Asset Aliases.can_read + - Viewer + * - ``/api/v2/assets/aliases/{asset_alias_id}`` + - GET + - Asset Aliases.can_read + - Viewer + * - ``/api/v2/assets/events`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/events`` + - POST + - Assets.can_create + - User + * - ``/api/v2/assets/{asset_id}`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}`` + - GET + - Asset Aliases.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}/materialize`` + - POST + - Assets.can_create + - User + * - ``/api/v2/assets/{asset_id}/queuedEvents`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/assets/{asset_id}/queuedEvents`` + - DELETE + - DAGs.can_edit + - User + * - ``/api/v2/assets/{asset_id}/queuedEvents`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}/state-store`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/assets/{asset_id}/state-store`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` + - PUT + - Assets.can_edit + - Op + * - ``/api/v2/auth/login`` + - GET + - None + - Public + * - ``/api/v2/auth/logout`` + - GET + - None + - Public + * - ``/api/v2/backfills`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/backfills`` + - POST + - DAGs.can_edit, DAG Runs.can_create + - User + * - ``/api/v2/backfills`` + - PUT + - DAGs.can_edit, DAG Runs.can_edit + - User + * - ``/api/v2/config`` + - GET + - Configurations.can_read + - Op + * - ``/api/v2/config/section/{section}/option/{option}`` + - GET + - Configurations.can_read + - Op + * - ``/api/v2/connections`` + - GET + - Connections.can_read + - Op + * - ``/api/v2/connections`` + - PATCH + - Connections.can_read + - Op + * - ``/api/v2/connections`` + - POST + - Connections.can_create + - Op + * - ``/api/v2/connections/defaults`` + - POST + - Connections.can_create + - Op + * - ``/api/v2/connections/enqueue-test`` + - GET + - None + - Public + * - ``/api/v2/connections/enqueue-test`` + - POST + - None + - Public + * - ``/api/v2/connections/test`` + - POST + - Connections.can_create + - Op + * - ``/api/v2/connections/{connection_id}`` + - DELETE + - Connections.can_delete + - Op + * - ``/api/v2/connections/{connection_id}`` + - GET + - Connections.can_read + - Op + * - ``/api/v2/connections/{connection_id}`` + - PATCH + - Connections.can_edit + - Op + * - ``/api/v2/dagSources/{dag_id}`` + - GET + - DAGs.can_read, DAG Code.can_read + - Viewer + * - ``/api/v2/dagStats`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dagTags`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dagWarnings`` + - GET + - DAGs.can_read, DAG Warnings.can_read + - Viewer + * - ``/api/v2/dags`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags`` + - PATCH + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}`` + - DELETE + - DAGs.can_delete + - User + * - ``/api/v2/dags/{dag_id}`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}`` + - PATCH + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` + - DELETE + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` + - DELETE + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/clearDagRuns`` + - POST + - DAGs.can_edit, DAG Runs.can_read + - User + * - ``/api/v2/dags/{dag_id}/clearPartitions`` + - POST + - DAGs.can_edit, DAG Runs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/clearTaskInstances`` + - POST + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns`` + - PATCH + - DAGs.can_edit, DAG Runs.can_read + - User + * - ``/api/v2/dags/{dag_id}/dagRuns`` + - POST + - DAGs.can_edit, DAG Runs.can_create + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/list`` + - POST + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` + - DELETE + - DAGs.can_edit, DAG Runs.can_delete + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/clear`` + - POST + - DAGs.can_edit, DAG Runs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/hitlDetails`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskGroupInstances/{group_id}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskGroupInstances/{group_id}/dry_run`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/list`` + - POST + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` + - DELETE + - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/dependencies`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/dry_run`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/externalLogUrl/{try_number}`` + - GET + - DAGs.can_read, Task Logs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/links`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/listMapped`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/logs/{try_number}`` + - GET + - DAGs.can_read, Task Logs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store`` + - DELETE + - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` + - DELETE + - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` + - PUT + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/tries`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/tries/{task_try_number}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries`` + - GET + - DAGs.can_read, XComs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries`` + - POST + - DAGs.can_edit, XComs.can_create + - Op + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` + - DELETE + - DAGs.can_edit, XComs.can_delete + - Op + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` + - GET + - DAGs.can_read, XComs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` + - PATCH + - DAGs.can_edit, XComs.can_edit + - Op + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/dependencies`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/dry_run`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails`` + - PATCH + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails/tries/{try_number}`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/tries`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/tries/{task_try_number}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/upstreamAssetEvents`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/upstreamAssetEvents`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/wait`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagVersions`` + - GET + - DAGs.can_read, DAG Versions.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagVersions/{version_number}`` + - GET + - DAGs.can_read, DAG Versions.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/details`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/favorite`` + - POST + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/tasks`` + - GET + - DAGs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/tasks/{task_id}`` + - GET + - DAGs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/unfavorite`` + - POST + - DAGs.can_read + - Viewer + * - ``/api/v2/eventLogs`` + - GET + - DAGs.can_read, Audit Logs.can_read + - Admin + * - ``/api/v2/eventLogs/{event_log_id}`` + - GET + - DAGs.can_read, Audit Logs.can_read + - Admin + * - ``/api/v2/importErrors`` + - GET + - ImportError.can_read + - Viewer + * - ``/api/v2/importErrors/{import_error_id}`` + - GET + - ImportError.can_read + - Viewer + * - ``/api/v2/jobs`` + - GET + - Jobs.can_read + - Viewer + * - ``/api/v2/monitor/health`` + - GET + - None + - Public + * - ``/api/v2/parseDagFile/{file_token}`` + - PUT + - DAGs.can_edit + - User + * - ``/api/v2/plugins`` + - GET + - Plugins.can_read + - Op + * - ``/api/v2/plugins/importErrors`` + - GET + - Plugins.can_read + - Op + * - ``/api/v2/pools`` + - GET + - Pools.can_read + - Viewer + * - ``/api/v2/pools`` + - PATCH + - Pools.can_read + - Viewer + * - ``/api/v2/pools`` + - POST + - Pools.can_create + - Op + * - ``/api/v2/pools/{pool_name:path}`` + - DELETE + - Pools.can_delete + - Op + * - ``/api/v2/pools/{pool_name:path}`` + - GET + - Pools.can_read + - Viewer + * - ``/api/v2/pools/{pool_name:path}`` + - PATCH + - Pools.can_edit + - Op + * - ``/api/v2/providers`` + - GET + - Providers.can_read + - Op + * - ``/api/v2/variables`` + - GET + - Variables.can_read + - Op + * - ``/api/v2/variables`` + - PATCH + - Variables.can_read + - Op + * - ``/api/v2/variables`` + - POST + - Variables.can_create + - Op + * - ``/api/v2/variables/{variable_key:path}`` + - DELETE + - Variables.can_delete + - Op + * - ``/api/v2/variables/{variable_key:path}`` + - GET + - Variables.can_read + - Op + * - ``/api/v2/variables/{variable_key:path}`` + - PATCH + - Variables.can_edit + - Op + * - ``/api/v2/version`` + - GET + - None + - Public diff --git a/providers/fab/docs/auth-manager/access-control.rst b/providers/fab/docs/auth-manager/access-control.rst index 52658bd6e80dc..bb3aa1a95489c 100644 --- a/providers/fab/docs/auth-manager/access-control.rst +++ b/providers/fab/docs/auth-manager/access-control.rst @@ -145,73 +145,13 @@ For individual Dags, the resource name is ``Dag:`` + the Dag ID. For example, if a user is trying to view Dag information for the ``example_dag_id``, and the endpoint requires ``DAGs.can_read`` access, access will be granted if the user has either ``DAGs.can_read`` or ``DAG:example_dag_id.can_read`` access. -================================================================================== ====== ================================================================= ============ Stable API Permissions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Endpoint Method Permissions Minimum Role -================================================================================== ====== ================================================================= ============ -/config GET Configurations.can_read Op -/connections GET Connections.can_read Op -/connections POST Connections.can_create Op -/connections/{connection_id} DELETE Connections.can_delete Op -/connections/{connection_id} PATCH Connections.can_edit Op -/connections/{connection_id} GET Connections.can_read Op -/dagSources/{file_token} GET Dag Code.can_read Viewer -/dags GET Dags.can_read Viewer -/dags/{dag_id} GET Dags.can_read Viewer -/dags/{dag_id} PATCH Dags.can_edit User -/dags/{dag_id}/clearTaskInstances PUT Dags.can_edit, Dag Runs.can_edit, Task Instances.can_edit User -/dags/{dag_id}/details GET Dags.can_read Viewer -/dags/{dag_id}/tasks GET Dags.can_read, Task Instances.can_read Viewer -/dags/{dag_id}/tasks/{task_id} GET Dags.can_read, Task Instances.can_read Viewer -/dags/{dag_id}/dagRuns GET Dags.can_read, Dag Runs.can_read Viewer -/dags/{dag_id}/dagRuns POST Dags.can_edit, Dag Runs.can_create User -/dags/{dag_id}/dagRuns/{dag_run_id} DELETE Dags.can_edit, Dag Runs.can_delete User -/dags/{dag_id}/dagRuns/{dag_run_id} GET Dags.can_read, Dag Runs.can_read Viewer -/dags/~/dagRuns/list POST Dags.can_edit, Dag Runs.can_read User -/assets GET Assets.can_read Viewer -/assets/{uri} GET Assets.can_read Viewer -/assets/events GET Assets.can_read Viewer -/eventLogs GET Audit Logs.can_read Admin - All Audit Logs.can_read (for rows not tied to a Dag) -/eventLogs/{event_log_id} GET Audit Logs.can_read Admin - All Audit Logs.can_read (for rows not tied to a Dag) -/importErrors GET ImportError.can_read Viewer -/importErrors/{import_error_id} GET ImportError.can_read Viewer -/health GET None Public -/version GET None Public -/pools GET Pools.can_read Op -/pools POST Pools.can_create Op -/pools/{pool_name} DELETE Pools.can_delete Op -/pools/{pool_name} GET Pools.can_read Op -/pools/{pool_name} PATCH Pools.can_edit Op -/providers GET Providers.can_read Op -/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances GET Dags.can_read, Dag Runs.can_read, Task Instances.can_read Viewer -/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id} GET Dags.can_read, Dag Runs.can_read, Task Instances.can_read Viewer -/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/links GET Dags.can_read, Dag Runs.can_read, Task Instances.can_read Viewer -/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/logs/{task_try_number} GET Dags.can_read, Dag Runs.can_read, Task Instances.can_read Viewer -/dags/~/dagRuns/~/taskInstances/list POST Dags.can_edit, Dag Runs.can_read, Task Instances.can_read User -/variables GET Variables.can_read Op -/variables POST Variables.can_create Op -/variables/{variable_key} DELETE Variables.can_delete Op -/variables/{variable_key} GET Variables.can_read Op -/variables/{variable_key} PATCH Variables.can_edit Op -/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries GET Dags.can_read, Dag Runs.can_read, Viewer - Task Instances.can_read, XComs.can_read -/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key} GET Dags.can_read, Dag Runs.can_read, Viewer - Task Instances.can_read, XComs.can_read -/users GET Users.can_read Admin -/users POST Users.can_create Admin -/users/{username} GET Users.can_read Admin -/users/{username} PATCH Users.can_edit Admin -/users/{username} DELETE Users.can_delete Admin -/roles GET Roles.can_read Admin -/roles POST Roles.can_create Admin -/roles/{role_name} GET Roles.can_read Admin -/roles/{role_name} PATCH Roles.can_edit Admin -/roles/{role_name} DELETE Roles.can_delete Admin -/permissions GET Permission Views.can_read Admin -================================================================================== ====== ================================================================= ============ +---------------------- + +The table below is generated from the API route definitions. Do not edit it by hand; +run ``prek run generate-fab-permissions-doc --all-files`` to regenerate it. + +.. include:: _api_permissions_table.rst ====================================== ======================================================================= ============ diff --git a/scripts/ci/prek/fab_permissions_doc.py b/scripts/ci/prek/fab_permissions_doc.py new file mode 100755 index 0000000000000..1dabfe2b61ab4 --- /dev/null +++ b/scripts/ci/prek/fab_permissions_doc.py @@ -0,0 +1,353 @@ +#!/usr/bin/env 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. +""" +Generate the FAB auth manager's REST API permission table. + +``extract_permissions.py`` produces the auth-manager-agnostic reference table +(``Resource`` + ``Required permission``) for airflow-core. The FAB provider +documents the same endpoints in its own vocabulary -- concrete permission names +such as ``DAGs.can_read`` plus the minimum built-in role that grants them -- +and that table is still maintained by hand, so it drifts. + +This module reuses ``extract_permissions.py``'s parser and renders the FAB view +from the same entries, so both tables come from one source of truth. + +Like ``extract_permissions.py`` this runs entirely statically: route files, the +FAB resource maps and the FAB role definitions are all read with Python's AST +parser, so no Airflow or FAB import is required. +""" + +from __future__ import annotations + +import ast +import pathlib +import sys + +from extract_permissions import ( + PUBLIC_ROUTES_DIR, + PermissionEntry, + extract_all_permissions, +) + +REPO_ROOT = pathlib.Path(__file__).resolve().parents[3] +FAB_SRC = REPO_ROOT / "providers/fab/src/airflow/providers/fab" +PERMISSIONS_PY = FAB_SRC / "www/security/permissions.py" +AUTH_MANAGER_PY = FAB_SRC / "auth_manager/fab_auth_manager.py" +SECURITY_MANAGER_PY = FAB_SRC / "auth_manager/security_manager/override.py" +OUTPUT_RST = REPO_ROOT / "providers/fab/docs/auth-manager/_api_permissions_table.rst" + +# HTTP verb -> FAB action. PATCH and PUT are both edits. +_METHOD_TO_ACTION = { + "GET": "can_read", + "POST": "can_create", + "PUT": "can_edit", + "PATCH": "can_edit", + "DELETE": "can_delete", +} + +# Built-in roles, ordered from least to most privileged. ``ROLE_CONFIGS`` builds +# each one as a superset of the previous, so the minimum role that grants a set +# of permissions is the first entry here whose permissions cover them all. +_ROLE_ORDER = ("Public", "Viewer", "User", "Op", "Admin") + + +def _resolve_str_constants(tree: ast.Module) -> dict[str, str]: + """Collect module-level ``NAME = "value"`` string assignments.""" + out: dict[str, str] = {} + for node in tree.body: + if isinstance(node, ast.Assign) and isinstance(node.value, ast.Constant): + if isinstance(node.value.value, str): + for target in node.targets: + if isinstance(target, ast.Name): + out[target.id] = node.value.value + return out + + +def _parse(path: pathlib.Path) -> ast.Module: + return ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + + +def _resource_display_names() -> dict[str, str]: + """``RESOURCE_DAG`` -> ``DAGs``, straight from ``permissions.py``.""" + return _resolve_str_constants(_parse(PERMISSIONS_PY)) + + +def _dict_literal(tree: ast.Module, name: str) -> ast.Dict | None: + for node in tree.body: + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == name: + if isinstance(node.value, ast.Dict): + return node.value + if isinstance(node, ast.AnnAssign): + if isinstance(node.target, ast.Name) and node.target.id == name: + if isinstance(node.value, ast.Dict): + return node.value + return None + + +def _enum_member(node: ast.expr) -> str | None: + """``DagAccessEntity.TASK_INSTANCE`` -> ``TASK_INSTANCE``.""" + if isinstance(node, ast.Attribute): + return node.attr + return None + + +def _const_names(node: ast.expr) -> tuple[str, ...]: + """A ``RESOURCE_X`` name, or a tuple of them, as their identifier strings.""" + if isinstance(node, ast.Name): + return (node.id,) + if isinstance(node, ast.Tuple): + return tuple(e.id for e in node.elts if isinstance(e, ast.Name)) + return () + + +def _entity_maps() -> tuple[dict[str, tuple[str, ...]], dict[str, str]]: + """Read FAB's two mapping dicts rather than restating them here. + + Restating them would create exactly the second source of truth this + generator exists to remove. + """ + tree = _parse(AUTH_MANAGER_PY) + + dag_map: dict[str, tuple[str, ...]] = {} + node = _dict_literal(tree, "_MAP_DAG_ACCESS_ENTITY_TO_FAB_RESOURCE_TYPE") + if node is not None: + for key, value in zip(node.keys, node.values): + member = _enum_member(key) if key is not None else None + if member: + dag_map[member] = _const_names(value) + + view_map: dict[str, str] = {} + node = _dict_literal(tree, "_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE") + if node is not None: + for key, value in zip(node.keys, node.values): + member = _enum_member(key) if key is not None else None + names = _const_names(value) + if member and names: + view_map[member] = names[0] + + return dag_map, view_map + + +def _role_permissions() -> dict[str, set[tuple[str, str]]]: + """Build ``{role: {(action, resource), ...}}`` from ``override.py``. + + ``VIEWER_PERMISSIONS`` and friends are class-level list literals of + ``(ACTION_*, RESOURCE_*)`` tuples; ``ROLE_CONFIGS`` then concatenates them. + Both are parsed statically. + """ + tree = _parse(SECURITY_MANAGER_PY) + consts = _resolve_str_constants(tree) + + groups: dict[str, set[tuple[str, str]]] = {} + for node in ast.walk(tree): + if not isinstance(node, ast.Assign) or not isinstance(node.value, ast.List): + continue + for target in node.targets: + if not isinstance(target, ast.Name) or not target.id.endswith("_PERMISSIONS"): + continue + pairs: set[tuple[str, str]] = set() + for elt in node.value.elts: + if not isinstance(elt, ast.Tuple) or len(elt.elts) != 2: + continue + action = _name_of(elt.elts[0]) + resource = _name_of(elt.elts[1]) + if action and resource: + pairs.add((action, resource)) + groups[target.id] = pairs + + # ROLE_CONFIGS is cumulative; rebuild that stacking explicitly. + roles: dict[str, set[tuple[str, str]]] = {"Public": set()} + roles["Viewer"] = set(groups.get("VIEWER_PERMISSIONS", set())) + roles["User"] = roles["Viewer"] | groups.get("USER_PERMISSIONS", set()) + roles["Op"] = roles["User"] | groups.get("OP_PERMISSIONS", set()) + roles["Admin"] = roles["Op"] | groups.get("ADMIN_PERMISSIONS", set()) + + # Resolve ACTION_*/RESOURCE_* identifiers to their string values. + perm_consts = _resolve_str_constants(_parse(PERMISSIONS_PY)) + perm_consts.update(consts) + resolved: dict[str, set[tuple[str, str]]] = {} + for role, pairs in roles.items(): + resolved[role] = {(perm_consts.get(a, a), perm_consts.get(r, r)) for a, r in pairs} + return resolved + + +def _name_of(node: ast.expr) -> str | None: + if isinstance(node, ast.Name): + return node.id + if isinstance(node, ast.Attribute): + return node.attr + return None + + +def fab_permissions_for(entry: PermissionEntry) -> list[str]: + """Translate one generic entry into FAB permission strings.""" + display = _resource_display_names() + dag_map, view_map = _entity_maps() + + if entry.resource == "Public": + return [] + + if entry.resource == "View" or entry.resource.startswith("View."): + # ``_build_resource_label`` renders these as ``View.PLUGINS``; the view name is + # also carried in ``required_permission``. + view_name = entry.resource.split(".", 1)[1] if "." in entry.resource else entry.required_permission + const = view_map.get(view_name) + if const is None: + return [] + return [f"{display.get(const, const)}.can_read"] + + # ``required_permission`` is the method passed to ``requires_access_*``, which is + # not always the route's HTTP verb: ``/clearTaskInstances`` is a POST route that + # authorizes with PUT. The authorization method is the one that decides the action. + auth_method = entry.required_permission + action = _METHOD_TO_ACTION.get(auth_method, "can_read") + + if entry.resource.startswith("DAG."): + entity = entry.resource.split(".", 1)[1] + consts = dag_map.get(entity, ()) + perms = [f"{display.get(c, c)}.{action}" for c in consts] + # A sub-entity check always runs the base Dag check first: ``is_authorized_dag`` + # uses GET for a read and PUT for anything else, so the base requirement is + # can_read on GET and can_edit otherwise -- it is present on GET routes too. + base = "can_read" if auth_method == "GET" else "can_edit" + dag_name = display.get("RESOURCE_DAG", "DAGs") + return [f"{dag_name}.{base}", *perms] + + const_name = f"RESOURCE_{_RESOURCE_TO_CONST.get(entry.resource, entry.resource.upper())}" + return [f"{display.get(const_name, entry.resource)}.{action}"] + + +# Generic resource label -> the RESOURCE_* suffix in permissions.py. +_RESOURCE_TO_CONST = { + "DAG": "DAG", + "Pool": "POOL", + "Connection": "CONNECTION", + "Configuration": "CONFIG", + "Variable": "VARIABLE", + "Asset": "ASSET", + "AssetAlias": "ASSET_ALIAS", +} + + +def minimum_role(perms: list[str]) -> str: + """First built-in role whose permissions cover every required permission.""" + if not perms: + return "Public" + roles = _role_permissions() + required = set() + for perm in perms: + resource, _, action = perm.rpartition(".") + required.add((action, resource)) + for role in _ROLE_ORDER: + if required <= roles.get(role, set()): + return role + return "Admin" + + +# The generated file must carry the ASF header itself; otherwise the ``insert-license`` +# hook adds it and this generator strips it back out on the next run. +RST_LICENSE_HEADER = """\ + .. 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. +""" + + +def render_rst(entries: list[PermissionEntry]) -> str: + rows = [] + for entry in sorted(entries): + perms = fab_permissions_for(entry) + rows.append( + ( + entry.full_path, + entry.http_method, + ", ".join(perms) if perms else "None", + minimum_role(perms), + ) + ) + + head = ("Endpoint", "Method", "Permissions", "Minimum role") + lines = [ + *RST_LICENSE_HEADER.split("\n"), + ".. THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.", + " Regenerate with: python scripts/ci/prek/fab_permissions_doc.py", + " Trigger: prek run generate-fab-permissions-doc --all-files", + "", + ".. list-table:: Stable REST API permissions (FAB auth manager)", + " :header-rows: 1", + " :widths: 45 8 32 15", + "", + f" * - {head[0]}", + f" - {head[1]}", + f" - {head[2]}", + f" - {head[3]}", + ] + for row_path, row_method, row_perms, row_role in rows: + lines += [ + f" * - ``{row_path}``", + f" - {row_method}", + f" - {row_perms}", + f" - {row_role}", + ] + return "\n".join(lines) + "\n" + + +def main(argv: list[str] | None = None) -> int: + import argparse + + parser = argparse.ArgumentParser(description="Generate the FAB API permission table.") + parser.add_argument("--check", action="store_true", help="Exit 1 if the file on disk is stale.") + parser.add_argument("--print", dest="print_only", action="store_true", help="Print instead of write.") + args = parser.parse_args(argv) + + entries = extract_all_permissions(PUBLIC_ROUTES_DIR) + content = render_rst(entries) + + if args.print_only: + print(content) + return 0 + + if args.check: + if not OUTPUT_RST.exists() or OUTPUT_RST.read_text(encoding="utf-8") != content: + print(f"[FAIL] {OUTPUT_RST} is stale. Run: python {pathlib.Path(__file__).name}", file=sys.stderr) + return 1 + print(f"[OK] {OUTPUT_RST} is up to date.") + return 0 + + OUTPUT_RST.parent.mkdir(parents=True, exist_ok=True) + OUTPUT_RST.write_text(content, encoding="utf-8") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From d6c8cc57a01a973cefefbc6fa063542168fa4286 Mon Sep 17 00:00:00 2001 From: baha-bouali Date: Fri, 28 Aug 2026 19:23:58 +0100 Subject: [PATCH 2/4] Also cover FAB's own user and role endpoints --- .../auth-manager/_api_permissions_table.rst | 52 ++++++++++++-- scripts/ci/prek/fab_permissions_doc.py | 68 +++++++++++++++++++ 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/providers/fab/docs/auth-manager/_api_permissions_table.rst b/providers/fab/docs/auth-manager/_api_permissions_table.rst index 1e1e69c812693..f3d03c9737f50 100644 --- a/providers/fab/docs/auth-manager/_api_permissions_table.rst +++ b/providers/fab/docs/auth-manager/_api_permissions_table.rst @@ -29,11 +29,11 @@ - Minimum role * - ``/api/v2/assets`` - GET - - Assets.can_read + - Asset Aliases.can_read - Viewer * - ``/api/v2/assets`` - GET - - Asset Aliases.can_read + - Assets.can_read - Viewer * - ``/api/v2/assets/aliases`` - GET @@ -53,11 +53,11 @@ - User * - ``/api/v2/assets/{asset_id}`` - GET - - Assets.can_read + - Asset Aliases.can_read - Viewer * - ``/api/v2/assets/{asset_id}`` - GET - - Asset Aliases.can_read + - Assets.can_read - Viewer * - ``/api/v2/assets/{asset_id}/materialize`` - POST @@ -555,3 +555,47 @@ - GET - None - Public + * - ``/fab/v1/permissions`` + - GET + - Roles.can_read + - Admin + * - ``/fab/v1/roles`` + - GET + - Roles.can_read + - Admin + * - ``/fab/v1/roles`` + - POST + - Roles.can_create + - Admin + * - ``/fab/v1/roles/{name}`` + - DELETE + - Roles.can_delete + - Admin + * - ``/fab/v1/roles/{name}`` + - GET + - Roles.can_read + - Admin + * - ``/fab/v1/roles/{name}`` + - PATCH + - Roles.can_edit + - Admin + * - ``/fab/v1/users`` + - GET + - Users.can_read + - Admin + * - ``/fab/v1/users`` + - POST + - Users.can_create + - Admin + * - ``/fab/v1/users/{username}`` + - DELETE + - Users.can_delete + - Admin + * - ``/fab/v1/users/{username}`` + - GET + - Users.can_read + - Admin + * - ``/fab/v1/users/{username}`` + - PATCH + - Users.can_edit + - Admin diff --git a/scripts/ci/prek/fab_permissions_doc.py b/scripts/ci/prek/fab_permissions_doc.py index 1dabfe2b61ab4..075e662409be5 100755 --- a/scripts/ci/prek/fab_permissions_doc.py +++ b/scripts/ci/prek/fab_permissions_doc.py @@ -51,6 +51,12 @@ SECURITY_MANAGER_PY = FAB_SRC / "auth_manager/security_manager/override.py" OUTPUT_RST = REPO_ROOT / "providers/fab/docs/auth-manager/_api_permissions_table.rst" +# FAB serves its own user and role management endpoints from a separate router, using +# ``requires_fab_custom_view`` rather than the core ``requires_access_*`` helpers. They +# are real endpoints and belong in this table, so they are parsed here as well. +FAB_ROUTES_DIR = FAB_SRC / "auth_manager/api_fastapi/routes" +FAB_ROUTER_PY = FAB_ROUTES_DIR / "router.py" + # HTTP verb -> FAB action. PATCH and PUT are both edits. _METHOD_TO_ACTION = { "GET": "can_read", @@ -261,6 +267,65 @@ def minimum_role(perms: list[str]) -> str: return "Admin" +def _fab_router_prefix() -> str: + """Read ``FAB_AUTH_PREFIX`` from router.py rather than hardcoding it.""" + return _resolve_str_constants(_parse(FAB_ROUTER_PY)).get("FAB_AUTH_PREFIX", "") + + +def _fab_route_entries() -> list[tuple[str, str, str, str]]: + """Parse FAB's own routes into ``(path, http_method, permissions, role)`` rows. + + ``requires_fab_custom_view(method, resource)`` names its resource directly, so no + entity expansion or base-Dag rule applies here -- unlike the core helpers. + """ + display = _resource_display_names() + prefix = _fab_router_prefix() + rows: list[tuple[str, str, str, str]] = [] + + for route_file in sorted(FAB_ROUTES_DIR.glob("*.py")): + if route_file.name in {"__init__.py", "router.py"}: + continue + for node in ast.walk(_parse(route_file)): + if not isinstance(node, ast.FunctionDef): + continue + for deco in node.decorator_list: + if not isinstance(deco, ast.Call): + continue + verb = _name_of(deco.func) + if verb is None or verb.upper() not in _METHOD_TO_ACTION: + continue + if not deco.args or not isinstance(deco.args[0], ast.Constant): + continue + path = deco.args[0].value + if not isinstance(path, str): + continue + + perms: list[str] = [] + for kw in deco.keywords: + if kw.arg != "dependencies" or not isinstance(kw.value, ast.List): + continue + for dep in kw.value.elts: + if not isinstance(dep, ast.Call) or not dep.args: + continue + inner = dep.args[0] + if not isinstance(inner, ast.Call): + continue + if _name_of(inner.func) != "requires_fab_custom_view": + continue + if len(inner.args) < 2 or not isinstance(inner.args[0], ast.Constant): + continue + auth_method = inner.args[0].value + const = _name_of(inner.args[1]) + if not const or not isinstance(auth_method, str): + continue + action = _METHOD_TO_ACTION.get(auth_method, "can_read") + perms.append(f"{display.get(const, const)}.{action}") + + if perms: + rows.append((f"{prefix}{path}", verb.upper(), ", ".join(perms), minimum_role(perms))) + return rows + + # The generated file must carry the ASF header itself; otherwise the ``insert-license`` # hook adds it and this generator strips it back out on the next run. RST_LICENSE_HEADER = """\ @@ -296,6 +361,9 @@ def render_rst(entries: list[PermissionEntry]) -> str: ) ) + rows.extend(_fab_route_entries()) + rows.sort() + head = ("Endpoint", "Method", "Permissions", "Minimum role") lines = [ *RST_LICENSE_HEADER.split("\n"), From 225a20dc110c6d3bd481feefc969fa1dca88419a Mon Sep 17 00:00:00 2001 From: baha-bouali Date: Fri, 28 Aug 2026 23:33:20 +0100 Subject: [PATCH 3/4] Generate the table in place to avoid the glob toctree --- .../auth-manager/_api_permissions_table.rst | 601 ------------------ .../fab/docs/auth-manager/access-control.rst | 592 ++++++++++++++++- scripts/ci/prek/fab_permissions_doc.py | 57 +- 3 files changed, 614 insertions(+), 636 deletions(-) delete mode 100644 providers/fab/docs/auth-manager/_api_permissions_table.rst diff --git a/providers/fab/docs/auth-manager/_api_permissions_table.rst b/providers/fab/docs/auth-manager/_api_permissions_table.rst deleted file mode 100644 index f3d03c9737f50..0000000000000 --- a/providers/fab/docs/auth-manager/_api_permissions_table.rst +++ /dev/null @@ -1,601 +0,0 @@ - .. 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. - -.. THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY. - Regenerate with: python scripts/ci/prek/fab_permissions_doc.py - Trigger: prek run generate-fab-permissions-doc --all-files - -.. list-table:: Stable REST API permissions (FAB auth manager) - :header-rows: 1 - :widths: 45 8 32 15 - - * - Endpoint - - Method - - Permissions - - Minimum role - * - ``/api/v2/assets`` - - GET - - Asset Aliases.can_read - - Viewer - * - ``/api/v2/assets`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/assets/aliases`` - - GET - - Asset Aliases.can_read - - Viewer - * - ``/api/v2/assets/aliases/{asset_alias_id}`` - - GET - - Asset Aliases.can_read - - Viewer - * - ``/api/v2/assets/events`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/assets/events`` - - POST - - Assets.can_create - - User - * - ``/api/v2/assets/{asset_id}`` - - GET - - Asset Aliases.can_read - - Viewer - * - ``/api/v2/assets/{asset_id}`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/assets/{asset_id}/materialize`` - - POST - - Assets.can_create - - User - * - ``/api/v2/assets/{asset_id}/queuedEvents`` - - DELETE - - Assets.can_delete - - Op - * - ``/api/v2/assets/{asset_id}/queuedEvents`` - - DELETE - - DAGs.can_edit - - User - * - ``/api/v2/assets/{asset_id}/queuedEvents`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/assets/{asset_id}/state-store`` - - DELETE - - Assets.can_delete - - Op - * - ``/api/v2/assets/{asset_id}/state-store`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` - - DELETE - - Assets.can_delete - - Op - * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` - - PUT - - Assets.can_edit - - Op - * - ``/api/v2/auth/login`` - - GET - - None - - Public - * - ``/api/v2/auth/logout`` - - GET - - None - - Public - * - ``/api/v2/backfills`` - - GET - - DAGs.can_read, DAG Runs.can_read - - Viewer - * - ``/api/v2/backfills`` - - POST - - DAGs.can_edit, DAG Runs.can_create - - User - * - ``/api/v2/backfills`` - - PUT - - DAGs.can_edit, DAG Runs.can_edit - - User - * - ``/api/v2/config`` - - GET - - Configurations.can_read - - Op - * - ``/api/v2/config/section/{section}/option/{option}`` - - GET - - Configurations.can_read - - Op - * - ``/api/v2/connections`` - - GET - - Connections.can_read - - Op - * - ``/api/v2/connections`` - - PATCH - - Connections.can_read - - Op - * - ``/api/v2/connections`` - - POST - - Connections.can_create - - Op - * - ``/api/v2/connections/defaults`` - - POST - - Connections.can_create - - Op - * - ``/api/v2/connections/enqueue-test`` - - GET - - None - - Public - * - ``/api/v2/connections/enqueue-test`` - - POST - - None - - Public - * - ``/api/v2/connections/test`` - - POST - - Connections.can_create - - Op - * - ``/api/v2/connections/{connection_id}`` - - DELETE - - Connections.can_delete - - Op - * - ``/api/v2/connections/{connection_id}`` - - GET - - Connections.can_read - - Op - * - ``/api/v2/connections/{connection_id}`` - - PATCH - - Connections.can_edit - - Op - * - ``/api/v2/dagSources/{dag_id}`` - - GET - - DAGs.can_read, DAG Code.can_read - - Viewer - * - ``/api/v2/dagStats`` - - GET - - DAGs.can_read, DAG Runs.can_read - - Viewer - * - ``/api/v2/dagTags`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dagWarnings`` - - GET - - DAGs.can_read, DAG Warnings.can_read - - Viewer - * - ``/api/v2/dags`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dags`` - - PATCH - - DAGs.can_edit - - User - * - ``/api/v2/dags/{dag_id}`` - - DELETE - - DAGs.can_delete - - User - * - ``/api/v2/dags/{dag_id}`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}`` - - PATCH - - DAGs.can_edit - - User - * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` - - DELETE - - Assets.can_delete - - Op - * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` - - DELETE - - DAGs.can_edit - - User - * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` - - DELETE - - Assets.can_delete - - Op - * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` - - DELETE - - DAGs.can_edit - - User - * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/clearDagRuns`` - - POST - - DAGs.can_edit, DAG Runs.can_read - - User - * - ``/api/v2/dags/{dag_id}/clearPartitions`` - - POST - - DAGs.can_edit, DAG Runs.can_edit - - User - * - ``/api/v2/dags/{dag_id}/clearTaskInstances`` - - POST - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns`` - - GET - - DAGs.can_read, DAG Runs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns`` - - PATCH - - DAGs.can_edit, DAG Runs.can_read - - User - * - ``/api/v2/dags/{dag_id}/dagRuns`` - - POST - - DAGs.can_edit, DAG Runs.can_create - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/list`` - - POST - - DAGs.can_read, DAG Runs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` - - DELETE - - DAGs.can_edit, DAG Runs.can_delete - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` - - GET - - DAGs.can_read, DAG Runs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/clear`` - - POST - - DAGs.can_edit, DAG Runs.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/hitlDetails`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskGroupInstances/{group_id}`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskGroupInstances/{group_id}/dry_run`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/list`` - - POST - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` - - DELETE - - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/dependencies`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/dry_run`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/externalLogUrl/{try_number}`` - - GET - - DAGs.can_read, Task Logs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/links`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/listMapped`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/logs/{try_number}`` - - GET - - DAGs.can_read, Task Logs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store`` - - DELETE - - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` - - DELETE - - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` - - PUT - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/tries`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/tries/{task_try_number}`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries`` - - GET - - DAGs.can_read, XComs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries`` - - POST - - DAGs.can_edit, XComs.can_create - - Op - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` - - DELETE - - DAGs.can_edit, XComs.can_delete - - Op - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` - - GET - - DAGs.can_read, XComs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` - - PATCH - - DAGs.can_edit, XComs.can_edit - - Op - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/dependencies`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/dry_run`` - - PATCH - - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails`` - - PATCH - - DAGs.can_edit - - User - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails/tries/{try_number}`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/tries`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/tries/{task_try_number}`` - - GET - - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/upstreamAssetEvents`` - - GET - - Assets.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/upstreamAssetEvents`` - - GET - - DAGs.can_read, DAG Runs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/wait`` - - GET - - DAGs.can_read, DAG Runs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagVersions`` - - GET - - DAGs.can_read, DAG Versions.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/dagVersions/{version_number}`` - - GET - - DAGs.can_read, DAG Versions.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/details`` - - GET - - DAGs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/favorite`` - - POST - - DAGs.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/tasks`` - - GET - - DAGs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/tasks/{task_id}`` - - GET - - DAGs.can_read, Task Instances.can_read - - Viewer - * - ``/api/v2/dags/{dag_id}/unfavorite`` - - POST - - DAGs.can_read - - Viewer - * - ``/api/v2/eventLogs`` - - GET - - DAGs.can_read, Audit Logs.can_read - - Admin - * - ``/api/v2/eventLogs/{event_log_id}`` - - GET - - DAGs.can_read, Audit Logs.can_read - - Admin - * - ``/api/v2/importErrors`` - - GET - - ImportError.can_read - - Viewer - * - ``/api/v2/importErrors/{import_error_id}`` - - GET - - ImportError.can_read - - Viewer - * - ``/api/v2/jobs`` - - GET - - Jobs.can_read - - Viewer - * - ``/api/v2/monitor/health`` - - GET - - None - - Public - * - ``/api/v2/parseDagFile/{file_token}`` - - PUT - - DAGs.can_edit - - User - * - ``/api/v2/plugins`` - - GET - - Plugins.can_read - - Op - * - ``/api/v2/plugins/importErrors`` - - GET - - Plugins.can_read - - Op - * - ``/api/v2/pools`` - - GET - - Pools.can_read - - Viewer - * - ``/api/v2/pools`` - - PATCH - - Pools.can_read - - Viewer - * - ``/api/v2/pools`` - - POST - - Pools.can_create - - Op - * - ``/api/v2/pools/{pool_name:path}`` - - DELETE - - Pools.can_delete - - Op - * - ``/api/v2/pools/{pool_name:path}`` - - GET - - Pools.can_read - - Viewer - * - ``/api/v2/pools/{pool_name:path}`` - - PATCH - - Pools.can_edit - - Op - * - ``/api/v2/providers`` - - GET - - Providers.can_read - - Op - * - ``/api/v2/variables`` - - GET - - Variables.can_read - - Op - * - ``/api/v2/variables`` - - PATCH - - Variables.can_read - - Op - * - ``/api/v2/variables`` - - POST - - Variables.can_create - - Op - * - ``/api/v2/variables/{variable_key:path}`` - - DELETE - - Variables.can_delete - - Op - * - ``/api/v2/variables/{variable_key:path}`` - - GET - - Variables.can_read - - Op - * - ``/api/v2/variables/{variable_key:path}`` - - PATCH - - Variables.can_edit - - Op - * - ``/api/v2/version`` - - GET - - None - - Public - * - ``/fab/v1/permissions`` - - GET - - Roles.can_read - - Admin - * - ``/fab/v1/roles`` - - GET - - Roles.can_read - - Admin - * - ``/fab/v1/roles`` - - POST - - Roles.can_create - - Admin - * - ``/fab/v1/roles/{name}`` - - DELETE - - Roles.can_delete - - Admin - * - ``/fab/v1/roles/{name}`` - - GET - - Roles.can_read - - Admin - * - ``/fab/v1/roles/{name}`` - - PATCH - - Roles.can_edit - - Admin - * - ``/fab/v1/users`` - - GET - - Users.can_read - - Admin - * - ``/fab/v1/users`` - - POST - - Users.can_create - - Admin - * - ``/fab/v1/users/{username}`` - - DELETE - - Users.can_delete - - Admin - * - ``/fab/v1/users/{username}`` - - GET - - Users.can_read - - Admin - * - ``/fab/v1/users/{username}`` - - PATCH - - Users.can_edit - - Admin diff --git a/providers/fab/docs/auth-manager/access-control.rst b/providers/fab/docs/auth-manager/access-control.rst index bb3aa1a95489c..761becef418c2 100644 --- a/providers/fab/docs/auth-manager/access-control.rst +++ b/providers/fab/docs/auth-manager/access-control.rst @@ -148,10 +148,594 @@ For example, if a user is trying to view Dag information for the ``example_dag_i Stable API Permissions ---------------------- -The table below is generated from the API route definitions. Do not edit it by hand; -run ``prek run generate-fab-permissions-doc --all-files`` to regenerate it. - -.. include:: _api_permissions_table.rst +.. BEGIN GENERATED PERMISSIONS TABLE + +.. THE TABLE BELOW IS AUTO-GENERATED. DO NOT EDIT IT MANUALLY. + Regenerate with: python scripts/ci/prek/fab_permissions_doc.py + Trigger: prek run generate-fab-permissions-doc --all-files + +.. list-table:: Stable REST API permissions (FAB auth manager) + :header-rows: 1 + :widths: 45 8 32 15 + + * - Endpoint + - Method + - Permissions + - Minimum role + * - ``/api/v2/assets`` + - GET + - Asset Aliases.can_read + - Viewer + * - ``/api/v2/assets`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/aliases`` + - GET + - Asset Aliases.can_read + - Viewer + * - ``/api/v2/assets/aliases/{asset_alias_id}`` + - GET + - Asset Aliases.can_read + - Viewer + * - ``/api/v2/assets/events`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/events`` + - POST + - Assets.can_create + - User + * - ``/api/v2/assets/{asset_id}`` + - GET + - Asset Aliases.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}/materialize`` + - POST + - Assets.can_create + - User + * - ``/api/v2/assets/{asset_id}/queuedEvents`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/assets/{asset_id}/queuedEvents`` + - DELETE + - DAGs.can_edit + - User + * - ``/api/v2/assets/{asset_id}/queuedEvents`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}/state-store`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/assets/{asset_id}/state-store`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/assets/{asset_id}/state-store/{key:path}`` + - PUT + - Assets.can_edit + - Op + * - ``/api/v2/auth/login`` + - GET + - None + - Public + * - ``/api/v2/auth/logout`` + - GET + - None + - Public + * - ``/api/v2/backfills`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/backfills`` + - POST + - DAGs.can_edit, DAG Runs.can_create + - User + * - ``/api/v2/backfills`` + - PUT + - DAGs.can_edit, DAG Runs.can_edit + - User + * - ``/api/v2/config`` + - GET + - Configurations.can_read + - Op + * - ``/api/v2/config/section/{section}/option/{option}`` + - GET + - Configurations.can_read + - Op + * - ``/api/v2/connections`` + - GET + - Connections.can_read + - Op + * - ``/api/v2/connections`` + - PATCH + - Connections.can_read + - Op + * - ``/api/v2/connections`` + - POST + - Connections.can_create + - Op + * - ``/api/v2/connections/defaults`` + - POST + - Connections.can_create + - Op + * - ``/api/v2/connections/enqueue-test`` + - GET + - None + - Public + * - ``/api/v2/connections/enqueue-test`` + - POST + - None + - Public + * - ``/api/v2/connections/test`` + - POST + - Connections.can_create + - Op + * - ``/api/v2/connections/{connection_id}`` + - DELETE + - Connections.can_delete + - Op + * - ``/api/v2/connections/{connection_id}`` + - GET + - Connections.can_read + - Op + * - ``/api/v2/connections/{connection_id}`` + - PATCH + - Connections.can_edit + - Op + * - ``/api/v2/dagSources/{dag_id}`` + - GET + - DAGs.can_read, DAG Code.can_read + - Viewer + * - ``/api/v2/dagStats`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dagTags`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dagWarnings`` + - GET + - DAGs.can_read, DAG Warnings.can_read + - Viewer + * - ``/api/v2/dags`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags`` + - PATCH + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}`` + - DELETE + - DAGs.can_delete + - User + * - ``/api/v2/dags/{dag_id}`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}`` + - PATCH + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` + - DELETE + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/assets/queuedEvents`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` + - DELETE + - Assets.can_delete + - Op + * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` + - DELETE + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/clearDagRuns`` + - POST + - DAGs.can_edit, DAG Runs.can_read + - User + * - ``/api/v2/dags/{dag_id}/clearPartitions`` + - POST + - DAGs.can_edit, DAG Runs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/clearTaskInstances`` + - POST + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns`` + - PATCH + - DAGs.can_edit, DAG Runs.can_read + - User + * - ``/api/v2/dags/{dag_id}/dagRuns`` + - POST + - DAGs.can_edit, DAG Runs.can_create + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/list`` + - POST + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` + - DELETE + - DAGs.can_edit, DAG Runs.can_delete + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/clear`` + - POST + - DAGs.can_edit, DAG Runs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/hitlDetails`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskGroupInstances/{group_id}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskGroupInstances/{group_id}/dry_run`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/list`` + - POST + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` + - DELETE + - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/dependencies`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/dry_run`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/externalLogUrl/{try_number}`` + - GET + - DAGs.can_read, Task Logs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/links`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/listMapped`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/logs/{try_number}`` + - GET + - DAGs.can_read, Task Logs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store`` + - DELETE + - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` + - DELETE + - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}`` + - PUT + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/tries`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/tries/{task_try_number}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries`` + - GET + - DAGs.can_read, XComs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries`` + - POST + - DAGs.can_edit, XComs.can_create + - Op + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` + - DELETE + - DAGs.can_edit, XComs.can_delete + - Op + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` + - GET + - DAGs.can_read, XComs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}`` + - PATCH + - DAGs.can_edit, XComs.can_edit + - Op + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/dependencies`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/dry_run`` + - PATCH + - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails`` + - PATCH + - DAGs.can_edit + - User + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails/tries/{try_number}`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/tries`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/tries/{task_try_number}`` + - GET + - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/upstreamAssetEvents`` + - GET + - Assets.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/upstreamAssetEvents`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/wait`` + - GET + - DAGs.can_read, DAG Runs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagVersions`` + - GET + - DAGs.can_read, DAG Versions.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/dagVersions/{version_number}`` + - GET + - DAGs.can_read, DAG Versions.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/details`` + - GET + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/favorite`` + - POST + - DAGs.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/tasks`` + - GET + - DAGs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/tasks/{task_id}`` + - GET + - DAGs.can_read, Task Instances.can_read + - Viewer + * - ``/api/v2/dags/{dag_id}/unfavorite`` + - POST + - DAGs.can_read + - Viewer + * - ``/api/v2/eventLogs`` + - GET + - DAGs.can_read, Audit Logs.can_read + - Admin + * - ``/api/v2/eventLogs/{event_log_id}`` + - GET + - DAGs.can_read, Audit Logs.can_read + - Admin + * - ``/api/v2/importErrors`` + - GET + - ImportError.can_read + - Viewer + * - ``/api/v2/importErrors/{import_error_id}`` + - GET + - ImportError.can_read + - Viewer + * - ``/api/v2/jobs`` + - GET + - Jobs.can_read + - Viewer + * - ``/api/v2/monitor/health`` + - GET + - None + - Public + * - ``/api/v2/parseDagFile/{file_token}`` + - PUT + - DAGs.can_edit + - User + * - ``/api/v2/plugins`` + - GET + - Plugins.can_read + - Op + * - ``/api/v2/plugins/importErrors`` + - GET + - Plugins.can_read + - Op + * - ``/api/v2/pools`` + - GET + - Pools.can_read + - Viewer + * - ``/api/v2/pools`` + - PATCH + - Pools.can_read + - Viewer + * - ``/api/v2/pools`` + - POST + - Pools.can_create + - Op + * - ``/api/v2/pools/{pool_name:path}`` + - DELETE + - Pools.can_delete + - Op + * - ``/api/v2/pools/{pool_name:path}`` + - GET + - Pools.can_read + - Viewer + * - ``/api/v2/pools/{pool_name:path}`` + - PATCH + - Pools.can_edit + - Op + * - ``/api/v2/providers`` + - GET + - Providers.can_read + - Op + * - ``/api/v2/variables`` + - GET + - Variables.can_read + - Op + * - ``/api/v2/variables`` + - PATCH + - Variables.can_read + - Op + * - ``/api/v2/variables`` + - POST + - Variables.can_create + - Op + * - ``/api/v2/variables/{variable_key:path}`` + - DELETE + - Variables.can_delete + - Op + * - ``/api/v2/variables/{variable_key:path}`` + - GET + - Variables.can_read + - Op + * - ``/api/v2/variables/{variable_key:path}`` + - PATCH + - Variables.can_edit + - Op + * - ``/api/v2/version`` + - GET + - None + - Public + * - ``/fab/v1/permissions`` + - GET + - Roles.can_read + - Admin + * - ``/fab/v1/roles`` + - GET + - Roles.can_read + - Admin + * - ``/fab/v1/roles`` + - POST + - Roles.can_create + - Admin + * - ``/fab/v1/roles/{name}`` + - DELETE + - Roles.can_delete + - Admin + * - ``/fab/v1/roles/{name}`` + - GET + - Roles.can_read + - Admin + * - ``/fab/v1/roles/{name}`` + - PATCH + - Roles.can_edit + - Admin + * - ``/fab/v1/users`` + - GET + - Users.can_read + - Admin + * - ``/fab/v1/users`` + - POST + - Users.can_create + - Admin + * - ``/fab/v1/users/{username}`` + - DELETE + - Users.can_delete + - Admin + * - ``/fab/v1/users/{username}`` + - GET + - Users.can_read + - Admin + * - ``/fab/v1/users/{username}`` + - PATCH + - Users.can_edit + - Admin + +.. END GENERATED PERMISSIONS TABLE ====================================== ======================================================================= ============ diff --git a/scripts/ci/prek/fab_permissions_doc.py b/scripts/ci/prek/fab_permissions_doc.py index 075e662409be5..53e81dba46736 100755 --- a/scripts/ci/prek/fab_permissions_doc.py +++ b/scripts/ci/prek/fab_permissions_doc.py @@ -49,7 +49,12 @@ PERMISSIONS_PY = FAB_SRC / "www/security/permissions.py" AUTH_MANAGER_PY = FAB_SRC / "auth_manager/fab_auth_manager.py" SECURITY_MANAGER_PY = FAB_SRC / "auth_manager/security_manager/override.py" -OUTPUT_RST = REPO_ROOT / "providers/fab/docs/auth-manager/_api_permissions_table.rst" +# The table is written in place, between markers, inside the access-control page. +# A separate fragment file cannot live here: every ``.rst`` under the docs tree is +# scanned as a document, so a title-less include fragment trips the glob toctree. +OUTPUT_RST = REPO_ROOT / "providers/fab/docs/auth-manager/access-control.rst" +BEGIN_MARKER = ".. BEGIN GENERATED PERMISSIONS TABLE" +END_MARKER = ".. END GENERATED PERMISSIONS TABLE" # FAB serves its own user and role management endpoints from a separate router, using # ``requires_fab_custom_view`` rather than the core ``requires_access_*`` helpers. They @@ -326,28 +331,6 @@ def _fab_route_entries() -> list[tuple[str, str, str, str]]: return rows -# The generated file must carry the ASF header itself; otherwise the ``insert-license`` -# hook adds it and this generator strips it back out on the next run. -RST_LICENSE_HEADER = """\ - .. 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. -""" - - def render_rst(entries: list[PermissionEntry]) -> str: rows = [] for entry in sorted(entries): @@ -366,8 +349,9 @@ def render_rst(entries: list[PermissionEntry]) -> str: head = ("Endpoint", "Method", "Permissions", "Minimum role") lines = [ - *RST_LICENSE_HEADER.split("\n"), - ".. THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.", + BEGIN_MARKER, + "", + ".. THE TABLE BELOW IS AUTO-GENERATED. DO NOT EDIT IT MANUALLY.", " Regenerate with: python scripts/ci/prek/fab_permissions_doc.py", " Trigger: prek run generate-fab-permissions-doc --all-files", "", @@ -387,6 +371,7 @@ def render_rst(entries: list[PermissionEntry]) -> str: f" - {row_perms}", f" - {row_role}", ] + lines += ["", END_MARKER] return "\n".join(lines) + "\n" @@ -399,21 +384,31 @@ def main(argv: list[str] | None = None) -> int: args = parser.parse_args(argv) entries = extract_all_permissions(PUBLIC_ROUTES_DIR) - content = render_rst(entries) + table = render_rst(entries) if args.print_only: - print(content) + print(table) return 0 + current = OUTPUT_RST.read_text(encoding="utf-8") + start = current.find(BEGIN_MARKER) + end = current.find(END_MARKER) + if start == -1 or end == -1: + print(f"[FAIL] {OUTPUT_RST} is missing the generated-table markers.", file=sys.stderr) + return 1 + updated = current[:start] + table + current[end + len(END_MARKER) + 1 :] + if args.check: - if not OUTPUT_RST.exists() or OUTPUT_RST.read_text(encoding="utf-8") != content: - print(f"[FAIL] {OUTPUT_RST} is stale. Run: python {pathlib.Path(__file__).name}", file=sys.stderr) + if current != updated: + print( + f"[FAIL] {OUTPUT_RST} is stale. Run: python {pathlib.Path(__file__).name}", + file=sys.stderr, + ) return 1 print(f"[OK] {OUTPUT_RST} is up to date.") return 0 - OUTPUT_RST.parent.mkdir(parents=True, exist_ok=True) - OUTPUT_RST.write_text(content, encoding="utf-8") + OUTPUT_RST.write_text(updated, encoding="utf-8") return 0 From 9990b874a0c4938fd2bceb9a26cf302cfc8a0b3d Mon Sep 17 00:00:00 2001 From: baha-bouali Date: Fri, 28 Aug 2026 23:35:50 +0100 Subject: [PATCH 4/4] Generate the table in place to avoid the glob toctree --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 09b078adcbfcd..08e4c23ea1605 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -742,7 +742,7 @@ repos: ^providers/fab/src/airflow/providers/fab/www/security/permissions\.py$| ^scripts/ci/prek/extract_permissions\.py$| ^scripts/ci/prek/fab_permissions_doc\.py$| - ^providers/fab/docs/auth-manager/_api_permissions_table\.rst$ + ^providers/fab/docs/auth-manager/access-control\.rst$ pass_filenames: false - id: update-tested-versions name: Update tested Python/DB/Kubernetes versions in docs