This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun 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 dfeeeb4aee5 AIP-84 | Fix Merged Permission for Connections and Pools
(#47345)
dfeeeb4aee5 is described below
commit dfeeeb4aee5a37add9f64ad3fa8356b902dd15f0
Author: LIU ZHE YOU <[email protected]>
AuthorDate: Tue Mar 4 21:12:29 2025 +0800
AIP-84 | Fix Merged Permission for Connections and Pools (#47345)
* Fix security
- requires_access_connection: is_authorized_pool -> is_authorized_connection
- use lambda instead of new callback function
- add type hint for requires_access_<entity>
- user type hint should be BaseUser instead of optional BaseUser
* Fix conftest of api_fastapi
- retrieve auth_manger from app.state instead of creating a new one
---
airflow/api_fastapi/core_api/security.py | 31 +++++++++++--------------------
tests/api_fastapi/conftest.py | 15 ++++++++++-----
2 files changed, 21 insertions(+), 25 deletions(-)
diff --git a/airflow/api_fastapi/core_api/security.py
b/airflow/api_fastapi/core_api/security.py
index b1da04d6791..1b000afa72c 100644
--- a/airflow/api_fastapi/core_api/security.py
+++ b/airflow/api_fastapi/core_api/security.py
@@ -78,54 +78,45 @@ async def get_user_with_exception_handling(request:
Request) -> BaseUser | None:
def requires_access_dag(method: ResourceMethod, access_entity: DagAccessEntity
| None = None) -> Callable:
def inner(
+ user: Annotated[BaseUser, Depends(get_user)],
dag_id: str | None = None,
- user: Annotated[BaseUser | None, Depends(get_user)] = None,
) -> None:
- def callback():
- return get_auth_manager().is_authorized_dag(
+ _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
)
-
- _requires_access(
- is_authorized_callback=callback,
)
return inner
-def requires_access_pool(method: ResourceMethod) -> Callable:
+def requires_access_pool(method: ResourceMethod) -> Callable[[Request,
BaseUser], None]:
def inner(
request: Request,
- user: Annotated[BaseUser | None, Depends(get_user)] = None,
+ user: Annotated[BaseUser, Depends(get_user)],
) -> None:
pool_name = request.path_params.get("pool_name")
- def callback():
- return get_auth_manager().is_authorized_pool(
+ _requires_access(
+ is_authorized_callback=lambda:
get_auth_manager().is_authorized_pool(
method=method, details=PoolDetails(name=pool_name), user=user
)
-
- _requires_access(
- is_authorized_callback=callback,
)
return inner
-def requires_access_connection(method: ResourceMethod) -> Callable:
+def requires_access_connection(method: ResourceMethod) -> Callable[[Request,
BaseUser], None]:
def inner(
request: Request,
- user: Annotated[BaseUser | None, Depends(get_user)] = None,
+ user: Annotated[BaseUser, Depends(get_user)],
) -> None:
connection_id = request.path_params.get("connection_id")
- def callback():
- return get_auth_manager().is_authorized_pool(
+ _requires_access(
+ is_authorized_callback=lambda:
get_auth_manager().is_authorized_connection(
method=method,
details=ConnectionDetails(conn_id=connection_id), user=user
)
-
- _requires_access(
- is_authorized_callback=callback,
)
return inner
diff --git a/tests/api_fastapi/conftest.py b/tests/api_fastapi/conftest.py
index dfbd0b4b427..6149168f0bd 100644
--- a/tests/api_fastapi/conftest.py
+++ b/tests/api_fastapi/conftest.py
@@ -18,12 +18,12 @@ from __future__ import annotations
import datetime
import os
+from typing import TYPE_CHECKING
import pytest
from fastapi.testclient import TestClient
from airflow.api_fastapi.app import create_app
-from airflow.auth.managers.simple.simple_auth_manager import SimpleAuthManager
from airflow.auth.managers.simple.user import SimpleAuthManagerUser
from airflow.models import Connection
from airflow.models.dag_version import DagVersion
@@ -33,6 +33,9 @@ from airflow.providers.standard.operators.empty import
EmptyOperator
from tests_common.test_utils.config import conf_vars
from tests_common.test_utils.db import clear_db_connections,
parse_and_sync_to_db
+if TYPE_CHECKING:
+ from airflow.auth.managers.simple.simple_auth_manager import
SimpleAuthManager
+
@pytest.fixture
def test_client():
@@ -44,7 +47,8 @@ def test_client():
):
"airflow.auth.managers.simple.simple_auth_manager.SimpleAuthManager",
}
):
- auth_manager = SimpleAuthManager()
+ app = create_app()
+ auth_manager: SimpleAuthManager = app.state.auth_manager
# set time_very_before to 2014-01-01 00:00:00 and time_very_after to
tomorrow
# to make the JWT token always valid for all test cases with
time_machine
time_very_before = datetime.datetime(2014, 1, 1, 0, 0, 0)
@@ -57,7 +61,7 @@ def test_client():
**auth_manager.serialize_user(SimpleAuthManagerUser(username="test",
role="admin")),
}
)
- yield TestClient(create_app(), headers={"Authorization": f"Bearer
{token}"})
+ yield TestClient(app, headers={"Authorization": f"Bearer {token}"})
@pytest.fixture
@@ -75,11 +79,12 @@ def unauthorized_test_client():
):
"airflow.auth.managers.simple.simple_auth_manager.SimpleAuthManager",
}
):
- auth_manager = SimpleAuthManager()
+ app = create_app()
+ auth_manager: SimpleAuthManager = app.state.auth_manager
token = auth_manager._get_token_signer().generate_signed_token(
auth_manager.serialize_user(SimpleAuthManagerUser(username="dummy", role=None))
)
- yield TestClient(create_app(), headers={"Authorization": f"Bearer
{token}"})
+ yield TestClient(app, headers={"Authorization": f"Bearer {token}"})
@pytest.fixture