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

vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 04d9aaa62a1 Pass team name information to `is_authorized_dag` method 
(#55040)
04d9aaa62a1 is described below

commit 04d9aaa62a111809d213756750cb943a8ed83eb3
Author: Vincent <[email protected]>
AuthorDate: Fri Aug 29 09:37:32 2025 -0400

    Pass team name information to `is_authorized_dag` method (#55040)
---
 .../auth/managers/models/resource_details.py       |  1 +
 .../src/airflow/api_fastapi/core_api/security.py   |  6 +++-
 airflow-core/src/airflow/models/dag.py             | 14 +++++++++
 .../unit/api_fastapi/core_api/test_security.py     |  6 ++--
 airflow-core/tests/unit/models/test_dag.py         | 36 ++++++++++++++++++++++
 devel-common/src/tests_common/test_utils/db.py     |  7 +++++
 6 files changed, 67 insertions(+), 3 deletions(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/auth/managers/models/resource_details.py 
b/airflow-core/src/airflow/api_fastapi/auth/managers/models/resource_details.py
index 038f82b3067..aed68f27aa8 100644
--- 
a/airflow-core/src/airflow/api_fastapi/auth/managers/models/resource_details.py
+++ 
b/airflow-core/src/airflow/api_fastapi/auth/managers/models/resource_details.py
@@ -42,6 +42,7 @@ class DagDetails:
     """Represents the details of a DAG."""
 
     id: str | None = None
+    team_name: str | None = None
 
 
 @dataclass
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/security.py 
b/airflow-core/src/airflow/api_fastapi/core_api/security.py
index d4f6e11e7e9..258ca2dc13d 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/security.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/security.py
@@ -103,10 +103,14 @@ def requires_access_dag(
         user: GetUserDep,
     ) -> None:
         dag_id: str | None = request.path_params.get("dag_id")
+        team_name = DagModel.get_team_name(dag_id) if dag_id else None
 
         _requires_access(
             is_authorized_callback=lambda: 
get_auth_manager().is_authorized_dag(
-                method=method, access_entity=access_entity, 
details=DagDetails(id=dag_id), user=user
+                method=method,
+                access_entity=access_entity,
+                details=DagDetails(id=dag_id, team_name=team_name),
+                user=user,
             )
         )
 
diff --git a/airflow-core/src/airflow/models/dag.py 
b/airflow-core/src/airflow/models/dag.py
index 99f881c8055..fad01299377 100644
--- a/airflow-core/src/airflow/models/dag.py
+++ b/airflow-core/src/airflow/models/dag.py
@@ -76,6 +76,7 @@ from airflow.models.asset import (
 )
 from airflow.models.base import Base, StringID
 from airflow.models.dag_version import DagVersion
+from airflow.models.dagbundle import DagBundleModel
 from airflow.models.dagrun import RUN_ID_REGEX, DagRun
 from airflow.models.taskinstance import (
     TaskInstance,
@@ -83,6 +84,7 @@ from airflow.models.taskinstance import (
     clear_task_instances,
 )
 from airflow.models.tasklog import LogTemplate
+from airflow.models.team import Team
 from airflow.sdk import TaskGroup
 from airflow.sdk.definitions.asset import Asset, AssetAlias, AssetUniqueKey, 
BaseAsset
 from airflow.sdk.definitions.dag import DAG as TaskSDKDag, dag as 
task_sdk_dag_decorator
@@ -2269,6 +2271,18 @@ class DagModel(Base):
         # an empty dict as there's no asset info to get. This method should 
thus return None.
         return get_asset_triggered_next_run_info([self.dag_id], 
session=session).get(self.dag_id, None)
 
+    @staticmethod
+    @provide_session
+    def get_team_name(dag_id: str, session=NEW_SESSION) -> str | None:
+        """Return the team name associated to a Dag or None if it is not owned 
by a specific team."""
+        stmt = (
+            select(Team.name)
+            .join(DagBundleModel.teams)
+            .join(DagModel, DagModel.bundle_name == DagBundleModel.name)
+            .where(DagModel.dag_id == dag_id)
+        )
+        return session.scalar(stmt)
+
 
 STATICA_HACK = True
 globals()["kcah_acitats"[::-1].upper()] = False
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/test_security.py 
b/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
index 92b4dbf2e7b..f407fbc7f8f 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
@@ -84,25 +84,27 @@ class TestFastApiSecurity:
 
         auth_manager.get_user_from_token.assert_called_once_with(token_str)
 
+    @pytest.mark.db_test
     @patch("airflow.api_fastapi.core_api.security.get_auth_manager")
     async def test_requires_access_dag_authorized(self, mock_get_auth_manager):
         auth_manager = Mock()
         auth_manager.is_authorized_dag.return_value = True
         mock_get_auth_manager.return_value = auth_manager
         fastapi_request = Mock()
-        fastapi_request.path_params.return_value = {}
+        fastapi_request.path_params = {}
 
         requires_access_dag("GET", DagAccessEntity.CODE)(fastapi_request, 
Mock())
 
         auth_manager.is_authorized_dag.assert_called_once()
 
+    @pytest.mark.db_test
     @patch("airflow.api_fastapi.core_api.security.get_auth_manager")
     async def test_requires_access_dag_unauthorized(self, 
mock_get_auth_manager):
         auth_manager = Mock()
         auth_manager.is_authorized_dag.return_value = False
         mock_get_auth_manager.return_value = auth_manager
         fastapi_request = Mock()
-        fastapi_request.path_params.return_value = {}
+        fastapi_request.path_params = {}
 
         mock_request = Mock()
         mock_request.path_params.return_value = {}
diff --git a/airflow-core/tests/unit/models/test_dag.py 
b/airflow-core/tests/unit/models/test_dag.py
index b9af8f15f2c..867dc5aaaa5 100644
--- a/airflow-core/tests/unit/models/test_dag.py
+++ b/airflow-core/tests/unit/models/test_dag.py
@@ -21,6 +21,7 @@ import datetime
 import logging
 import os
 import pickle
+import uuid
 from datetime import timedelta
 from pathlib import Path
 from typing import TYPE_CHECKING
@@ -64,6 +65,7 @@ from airflow.models.dagbundle import DagBundleModel
 from airflow.models.dagrun import DagRun
 from airflow.models.serialized_dag import SerializedDagModel
 from airflow.models.taskinstance import TaskInstance as TI
+from airflow.models.team import Team
 from airflow.providers.standard.operators.bash import BashOperator
 from airflow.providers.standard.operators.empty import EmptyOperator
 from airflow.providers.standard.operators.python import PythonOperator
@@ -92,6 +94,7 @@ from tests_common.test_utils.db import (
     clear_db_dags,
     clear_db_runs,
     clear_db_serialized_dags,
+    clear_db_teams,
 )
 from tests_common.test_utils.mapping import expand_mapped_task
 from tests_common.test_utils.mock_plugins import mock_plugin_manager
@@ -145,6 +148,18 @@ def test_dags_bundle(configure_testing_dag_bundle):
         yield
 
 
[email protected]
+def testing_team():
+    from airflow.utils.session import create_session
+
+    with create_session() as session:
+        team = session.query(Team).filter_by(name="testing").one_or_none()
+        if not team:
+            team = Team(id=uuid.uuid4(), name="testing")
+            session.add(team)
+        yield team
+
+
 def _create_dagrun(
     dag: DAG,
     *,
@@ -2086,6 +2101,8 @@ class TestDagModel:
         clear_db_dags()
         clear_db_assets()
         clear_db_runs()
+        clear_db_dag_bundles()
+        clear_db_teams()
 
     def setup_method(self):
         self._clean()
@@ -2522,6 +2539,25 @@ class TestDagModel:
             ]
         }
 
+    def test_get_team_name(self, testing_team):
+        session = settings.Session()
+        dag_bundle = DagBundleModel(name="testing-team")
+        dag_bundle.teams.append(testing_team)
+        session.add(dag_bundle)
+        session.flush()
+
+        dag_id = "test_get_team_name"
+        dag = DAG(dag_id, schedule=None)
+        orm_dag = DagModel(
+            dag_id=dag.dag_id,
+            bundle_name="testing-team",
+            is_stale=False,
+        )
+        session.add(orm_dag)
+        session.flush()
+        assert DagModel.get_dagmodel(dag_id) is not None
+        assert DagModel.get_team_name(dag_id, session=session) == "testing"
+
 
 class TestQueries:
     def setup_method(self) -> None:
diff --git a/devel-common/src/tests_common/test_utils/db.py 
b/devel-common/src/tests_common/test_utils/db.py
index 5849ec5b1d1..28764aeb90d 100644
--- a/devel-common/src/tests_common/test_utils/db.py
+++ b/devel-common/src/tests_common/test_utils/db.py
@@ -355,6 +355,13 @@ def clear_db_dag_bundles():
         session.query(DagBundleModel).delete()
 
 
+def clear_db_teams():
+    with create_session() as session:
+        from airflow.models.team import Team
+
+        session.query(Team).delete()
+
+
 def clear_dag_specific_permissions():
     if "FabAuthManager" not in conf.get("core", "auth_manager"):
         return

Reply via email to