Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions airflow-core/docs/security/api_permissions_ref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ source code so it stays up to date as endpoints are added or changed.
- ``Asset``
- ``GET``
* - ``DELETE``
- ``/api/v2/assets/{asset_id}/state-store/{key:path}``
- ``/api/v2/assets/{asset_id}/state-store/{key}``
- ``Asset``
- ``DELETE``
* - ``GET``
- ``/api/v2/assets/{asset_id}/state-store/{key:path}``
- ``/api/v2/assets/{asset_id}/state-store/{key}``
- ``Asset``
- ``GET``
* - ``PUT``
- ``/api/v2/assets/{asset_id}/state-store/{key:path}``
- ``/api/v2/assets/{asset_id}/state-store/{key}``
- ``Asset``
- ``PUT``
* - ``GET``
Expand All @@ -126,8 +126,28 @@ source code so it stays up to date as endpoints are added or changed.
- ``/api/v2/backfills``
- ``DAG.RUN``
- ``POST``
* - ``POST``
- ``/api/v2/backfills/dry_run``
- ``DAG.RUN``
- ``POST``
* - ``GET``
- ``/api/v2/backfills/{backfill_id}``
- ``DAG.RUN``
- ``GET``
* - ``PUT``
- ``/api/v2/backfills``
- ``/api/v2/backfills/{backfill_id}/cancel``
- ``DAG.RUN``
- ``PUT``
* - ``GET``
- ``/api/v2/backfills/{backfill_id}/dag_runs``
- ``DAG.RUN``
- ``GET``
* - ``PUT``
- ``/api/v2/backfills/{backfill_id}/pause``
- ``DAG.RUN``
- ``PUT``
* - ``PUT``
- ``/api/v2/backfills/{backfill_id}/unpause``
- ``DAG.RUN``
- ``PUT``
* - ``GET``
Expand Down Expand Up @@ -375,19 +395,19 @@ source code so it stays up to date as endpoints are added or changed.
- ``DAG.TASK_INSTANCE``
- ``GET``
* - ``DELETE``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}``
- ``DAG.TASK_INSTANCE``
- ``DELETE``
* - ``GET``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}``
- ``DAG.TASK_INSTANCE``
- ``GET``
* - ``PATCH``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}``
- ``DAG.TASK_INSTANCE``
- ``PUT``
* - ``PUT``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}``
- ``DAG.TASK_INSTANCE``
- ``PUT``
* - ``GET``
Expand All @@ -407,15 +427,15 @@ source code so it stays up to date as endpoints are added or changed.
- ``DAG.XCOM``
- ``POST``
* - ``DELETE``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key}``
- ``DAG.XCOM``
- ``DELETE``
* - ``GET``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key}``
- ``DAG.XCOM``
- ``GET``
* - ``PATCH``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
- ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key}``
- ``DAG.XCOM``
- ``PUT``
* - ``GET``
Expand Down Expand Up @@ -547,15 +567,15 @@ source code so it stays up to date as endpoints are added or changed.
- ``Pool``
- ``POST``
* - ``DELETE``
- ``/api/v2/pools/{pool_name:path}``
- ``/api/v2/pools/{pool_name}``
- ``Pool``
- ``DELETE``
* - ``GET``
- ``/api/v2/pools/{pool_name:path}``
- ``/api/v2/pools/{pool_name}``
- ``Pool``
- ``GET``
* - ``PATCH``
- ``/api/v2/pools/{pool_name:path}``
- ``/api/v2/pools/{pool_name}``
- ``Pool``
- ``PUT``
* - ``GET``
Expand All @@ -575,15 +595,15 @@ source code so it stays up to date as endpoints are added or changed.
- ``Variable``
- ``POST``
* - ``DELETE``
- ``/api/v2/variables/{variable_key:path}``
- ``/api/v2/variables/{variable_key}``
- ``Variable``
- ``DELETE``
* - ``GET``
- ``/api/v2/variables/{variable_key:path}``
- ``/api/v2/variables/{variable_key}``
- ``Variable``
- ``GET``
* - ``PATCH``
- ``/api/v2/variables/{variable_key:path}``
- ``/api/v2/variables/{variable_key}``
- ``Variable``
- ``PUT``
* - ``GET``
Expand Down
54 changes: 37 additions & 17 deletions providers/fab/docs/auth-manager/access-control.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,15 @@ Stable API Permissions
- GET
- Assets.can_read
- Viewer
* - ``/api/v2/assets/{asset_id}/state-store/{key:path}``
* - ``/api/v2/assets/{asset_id}/state-store/{key}``
- DELETE
- Assets.can_delete
- Op
* - ``/api/v2/assets/{asset_id}/state-store/{key:path}``
* - ``/api/v2/assets/{asset_id}/state-store/{key}``
- GET
- Assets.can_read
- Viewer
* - ``/api/v2/assets/{asset_id}/state-store/{key:path}``
* - ``/api/v2/assets/{asset_id}/state-store/{key}``
- PUT
- Assets.can_edit
- Op
Expand All @@ -300,7 +300,27 @@ Stable API Permissions
- POST
- DAGs.can_edit, DAG Runs.can_create
- User
* - ``/api/v2/backfills``
* - ``/api/v2/backfills/dry_run``
- POST
- DAGs.can_edit, DAG Runs.can_create
- User
* - ``/api/v2/backfills/{backfill_id}``
- GET
- DAGs.can_read, DAG Runs.can_read
- Viewer
* - ``/api/v2/backfills/{backfill_id}/cancel``
- PUT
- DAGs.can_edit, DAG Runs.can_edit
- User
* - ``/api/v2/backfills/{backfill_id}/dag_runs``
- GET
- DAGs.can_read, DAG Runs.can_read
- Viewer
* - ``/api/v2/backfills/{backfill_id}/pause``
- PUT
- DAGs.can_edit, DAG Runs.can_edit
- User
* - ``/api/v2/backfills/{backfill_id}/unpause``
- PUT
- DAGs.can_edit, DAG Runs.can_edit
- User
Expand Down Expand Up @@ -548,19 +568,19 @@ Stable API Permissions
- 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}``
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}``
- 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}``
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}``
- 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}``
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}``
- 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}``
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}``
- PUT
- DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
- User
Expand All @@ -580,15 +600,15 @@ Stable API Permissions
- POST
- DAGs.can_edit, XComs.can_create
- Op
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key}``
- DELETE
- DAGs.can_edit, XComs.can_delete
- Op
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key}``
- GET
- DAGs.can_read, XComs.can_read
- Viewer
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
* - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key}``
- PATCH
- DAGs.can_edit, XComs.can_edit
- Op
Expand Down Expand Up @@ -720,15 +740,15 @@ Stable API Permissions
- POST
- Pools.can_create
- Op
* - ``/api/v2/pools/{pool_name:path}``
* - ``/api/v2/pools/{pool_name}``
- DELETE
- Pools.can_delete
- Op
* - ``/api/v2/pools/{pool_name:path}``
* - ``/api/v2/pools/{pool_name}``
- GET
- Pools.can_read
- Viewer
* - ``/api/v2/pools/{pool_name:path}``
* - ``/api/v2/pools/{pool_name}``
- PATCH
- Pools.can_edit
- Op
Expand All @@ -748,15 +768,15 @@ Stable API Permissions
- POST
- Variables.can_create
- Op
* - ``/api/v2/variables/{variable_key:path}``
* - ``/api/v2/variables/{variable_key}``
- DELETE
- Variables.can_delete
- Op
* - ``/api/v2/variables/{variable_key:path}``
* - ``/api/v2/variables/{variable_key}``
- GET
- Variables.can_read
- Op
* - ``/api/v2/variables/{variable_key:path}``
* - ``/api/v2/variables/{variable_key}``
- PATCH
- Variables.can_edit
- Op
Expand Down
35 changes: 31 additions & 4 deletions scripts/ci/prek/extract_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import ast
import pathlib
import re
import sys
from dataclasses import dataclass

Expand Down Expand Up @@ -95,6 +96,34 @@ def _resolve_string_node(node: ast.expr, module_consts: dict[str, str]) -> str:
return f"<unresolved:{ast.unparse(node)}>"


def strip_path_converters(path: str) -> str:
"""
Drop Starlette path converters from a route path.

A route declared as ``/{pool_name:path}`` is served, and documented in the
OpenAPI spec, as ``/{pool_name}``. The converter only tells Starlette how to
match the segment, so leaving it in prints a URL that no client ever calls.
"""
return re.sub(r"\{([^{}:]+):[^{}]+\}", r"{\1}", path)


def _extract_route_path(decorator: ast.Call, module_consts: dict[str, str]) -> str:
"""
Resolve the route path from a route decorator.

FastAPI accepts the path either positionally or as ``path=``, and both forms
are in use here. Reading only the positional form silently resolves the whole
file to its router prefix, which collapses every route onto one bogus row
instead of failing.
"""
if decorator.args:
return _resolve_string_node(decorator.args[0], module_consts)
path_kwarg = next((kw for kw in decorator.keywords if kw.arg == "path"), None)
if path_kwarg is not None:
return _resolve_string_node(path_kwarg.value, module_consts)
raise RouteParseError(f"Route decorator has no path: {ast.unparse(decorator)}")


def _extract_module_string_constants(tree: ast.Module) -> dict[str, str]:
"""
Walk top-level assignments and collect simple string assignments.
Expand Down Expand Up @@ -313,17 +342,15 @@ def extract_from_file(path: pathlib.Path) -> list[PermissionEntry]:
continue

# Resolve the route path
route_suffix = ""
if decorator.args:
route_suffix = _resolve_string_node(decorator.args[0], module_consts)
route_suffix = _extract_route_path(decorator, module_consts)

# Resolve the prefix based on the router variable used in the decorator
router_prefix = ""
if isinstance(decorator.func.value, ast.Name):
router_var = decorator.func.value.id
router_prefix = routers.get(router_var, "")

full_path = API_PREFIX + router_prefix + route_suffix
full_path = strip_path_converters(API_PREFIX + router_prefix + route_suffix)

# Extract tag (for grouping in the RST table)
tag = _extract_tag_from_decorator(decorator)
Expand Down
Loading