This is an automated email from the ASF dual-hosted git repository.

pierrejeambrun pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-2-test by this push:
     new 90c5b389fd4 [v3-2-test] Add per-DAG authorization to 
partitioned_dag_runs endpoints (#65344) (#65538)
90c5b389fd4 is described below

commit 90c5b389fd40d00f04f680faead7fd194f226471
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Apr 20 18:23:23 2026 +0200

    [v3-2-test] Add per-DAG authorization to partitioned_dag_runs endpoints 
(#65344) (#65538)
    
    The partitioned_dag_runs endpoints enforced only asset-level access
    control. Add requires_access_dag and ReadableDagsFilterDep to match
    the pattern used by the sibling next_run_assets endpoint in assets.py.
    (cherry picked from commit e36d10a5196d100adb3829219dc91be72d6a7fc9)
    
    
    Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
    https: 
//github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions
    
    Co-authored-by: Jarek Potiuk <[email protected]>
---
 .../core_api/routes/ui/partitioned_dag_runs.py     | 12 +++++++++--
 .../routes/ui/test_partitioned_dag_runs.py         | 24 ++++++++++++++++++++--
 2 files changed, 32 insertions(+), 4 deletions(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/partitioned_dag_runs.py
 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/partitioned_dag_runs.py
index f9090a6ece0..fe4696efbb5 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/partitioned_dag_runs.py
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/partitioned_dag_runs.py
@@ -31,7 +31,11 @@ from 
airflow.api_fastapi.core_api.datamodels.ui.partitioned_dag_runs import (
     PartitionedDagRunDetailResponse,
     PartitionedDagRunResponse,
 )
-from airflow.api_fastapi.core_api.security import requires_access_asset
+from airflow.api_fastapi.core_api.security import (
+    ReadableDagsFilterDep,
+    requires_access_asset,
+    requires_access_dag,
+)
 from airflow.models import DagModel
 from airflow.models.asset import (
     AssetModel,
@@ -63,6 +67,7 @@ def _build_response(row, required_count: int) -> 
PartitionedDagRunResponse:
 )
 def get_partitioned_dag_runs(
     session: SessionDep,
+    readable_dags_filter: ReadableDagsFilterDep,
     dag_id: QueryPartitionedDagRunDagIdFilter,
     has_created_dag_run_id: QueryPartitionedDagRunHasCreatedDagRunIdFilter,
 ) -> PartitionedDagRunCollectionResponse:
@@ -123,6 +128,9 @@ def get_partitioned_dag_runs(
         received_subq.label("total_received"),
     ).outerjoin(DagRun, AssetPartitionDagRun.created_dag_run_id == DagRun.id)
     query = apply_filters_to_select(statement=query, filters=[dag_id, 
has_created_dag_run_id])
+    readable_dag_ids = readable_dags_filter.value
+    if readable_dag_ids is not None:
+        query = 
query.where(AssetPartitionDagRun.target_dag_id.in_(readable_dag_ids))
     query = query.order_by(AssetPartitionDagRun.created_at.desc())
 
     if not (rows := session.execute(query).all()):
@@ -162,7 +170,7 @@ def get_partitioned_dag_runs(
 
 @partitioned_dag_runs_router.get(
     "/pending_partitioned_dag_run/{dag_id}/{partition_key}",
-    dependencies=[Depends(requires_access_asset(method="GET"))],
+    dependencies=[Depends(requires_access_asset(method="GET")), 
Depends(requires_access_dag(method="GET"))],
 )
 def get_pending_partitioned_dag_run(
     dag_id: str,
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_partitioned_dag_runs.py
 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_partitioned_dag_runs.py
index 658d23abb21..a2a86da327b 100644
--- 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_partitioned_dag_runs.py
+++ 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_partitioned_dag_runs.py
@@ -16,6 +16,8 @@
 # under the License.
 from __future__ import annotations
 
+from unittest import mock
+
 import pendulum
 import pytest
 from sqlalchemy import select
@@ -57,7 +59,7 @@ class TestGetPartitionedDagRuns:
         dag_maker.create_dagrun()
         dag_maker.sync_dagbag_to_db()
 
-        with assert_queries_count(2):
+        with assert_queries_count(3):
             resp = 
test_client.get("/partitioned_dag_runs?dag_id=normal&has_created_dag_run_id=false")
         assert resp.status_code == 200
         assert resp.json() == {"partitioned_dag_runs": [], "total": 0, 
"asset_expressions": None}
@@ -144,7 +146,7 @@ class TestGetPartitionedDagRuns:
             )
         session.commit()
 
-        with assert_queries_count(2):
+        with assert_queries_count(3):
             resp = test_client.get(
                 f"/partitioned_dag_runs?dag_id=list_dag"
                 
f"&has_created_dag_run_id={str(has_created_dag_run_id).lower()}"
@@ -218,6 +220,24 @@ class TestGetPartitionedDagRuns:
         assert pdr_resp["total_required"] == num_target_assets
         assert pdr_resp["total_received"] == received_count
 
+    @mock.patch(
+        
"airflow.api_fastapi.auth.managers.base_auth_manager.BaseAuthManager.get_authorized_dag_ids",
+        return_value={"other_dag"},
+    )
+    def test_partitioned_dag_runs_filters_unreadable_dags(self, _, 
test_client, dag_maker, session):
+        schedule = PartitionedAssetTimetable(assets=Asset(uri="s3://bucket/a", 
name="a"))
+        with dag_maker(dag_id="restricted_dag", schedule=schedule, 
serialized=True):
+            EmptyOperator(task_id="t")
+        dag_maker.sync_dagbag_to_db()
+        session.add(AssetPartitionDagRun(target_dag_id="restricted_dag", 
partition_key="2024-06-01"))
+        session.commit()
+
+        resp = 
test_client.get("/partitioned_dag_runs?has_created_dag_run_id=false")
+        assert resp.status_code == 200
+        body = resp.json()
+        dag_ids = {r["dag_id"] for r in body["partitioned_dag_runs"]}
+        assert "restricted_dag" not in dag_ids
+
 
 class TestGetPendingPartitionedDagRun:
     def test_should_response_401(self, unauthenticated_test_client):

Reply via email to