From 9fdcc36e22b84998921afedd10de9f5741c2d84d Mon Sep 17 00:00:00 2001 From: pierrejeambrun Date: Tue, 3 Feb 2026 17:20:57 +0100 Subject: [PATCH 1/4] Deprecate 'is_authorized_backfill' in providers --- .../providers/amazon/aws/auth_manager/aws_auth_manager.py | 8 ++++++++ .../providers/fab/auth_manager/fab_auth_manager.py | 8 +++++++- .../keycloak/auth_manager/keycloak_auth_manager.py | 8 ++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py b/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py index aa23b4f2634f3..4d570760462cf 100644 --- a/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py +++ b/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py @@ -16,6 +16,7 @@ # under the License. from __future__ import annotations +import warnings from collections import defaultdict from collections.abc import Sequence from functools import cached_property @@ -27,6 +28,7 @@ from airflow.api_fastapi.app import AUTH_MANAGER_FASTAPI_APP_PREFIX from airflow.api_fastapi.auth.managers.base_auth_manager import BaseAuthManager from airflow.cli.cli_config import CLICommand +from airflow.exceptions import AirflowProviderDeprecationWarning from airflow.providers.amazon.aws.auth_manager.avp.entities import AvpEntities from airflow.providers.amazon.aws.auth_manager.avp.facade import ( AwsAuthManagerAmazonVerifiedPermissionsFacade, @@ -158,6 +160,12 @@ def is_authorized_dag( def is_authorized_backfill( self, *, method: ResourceMethod, user: AwsAuthManagerUser, details: BackfillDetails | None = None ) -> bool: + warnings.warn( + "Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", + AirflowProviderDeprecationWarning, + stacklevel=2, + ) + backfill_id = details.id if details else None return self.avp_facade.is_authorized( method=method, entity_type=AvpEntities.BACKFILL, user=user, entity_id=backfill_id diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py index aebd4fa6f149c..0ffcd6d0dcb3f 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py @@ -17,6 +17,7 @@ # under the License. from __future__ import annotations +import warnings from functools import cached_property from pathlib import Path from typing import TYPE_CHECKING, Any @@ -51,7 +52,7 @@ ) from airflow.api_fastapi.common.types import ExtraMenuItem, MenuItem from airflow.configuration import conf -from airflow.exceptions import AirflowConfigException +from airflow.exceptions import AirflowConfigException, AirflowProviderDeprecationWarning from airflow.models import Connection, DagModel, Pool, Variable from airflow.providers.common.compat.sdk import AirflowException from airflow.providers.fab.auth_manager.models import Permission, Role, User @@ -389,6 +390,11 @@ def is_authorized_backfill( user: User, details: BackfillDetails | None = None, ) -> bool: + warnings.warn( + "Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", + AirflowProviderDeprecationWarning, + stacklevel=2, + ) return self._is_authorized(method=method, resource_type=RESOURCE_BACKFILL, user=user) def is_authorized_asset( diff --git a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py index dc8de7c25954e..1daca0ffd7429 100644 --- a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py +++ b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py @@ -19,6 +19,7 @@ import json import logging import time +import warnings from base64 import urlsafe_b64decode from typing import TYPE_CHECKING, Any from urllib.parse import urljoin @@ -32,6 +33,7 @@ from airflow.api_fastapi.app import AUTH_MANAGER_FASTAPI_APP_PREFIX from airflow.api_fastapi.auth.managers.base_auth_manager import BaseAuthManager +from airflow.exceptions import AirflowProviderDeprecationWarning try: from airflow.api_fastapi.auth.managers.base_auth_manager import ExtendedResourceMethod @@ -220,6 +222,12 @@ def is_authorized_dag( def is_authorized_backfill( self, *, method: ResourceMethod, user: KeycloakAuthManagerUser, details: BackfillDetails | None = None ) -> bool: + warnings.warn( + "Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", + AirflowProviderDeprecationWarning, + stacklevel=2, + ) + backfill_id = str(details.id) if details else None return self._is_authorized( method=method, resource_type=KeycloakResource.BACKFILL, user=user, resource_id=backfill_id From d3823669d03975032a934f6b33c54fa1db3a94e4 Mon Sep 17 00:00:00 2001 From: pierrejeambrun Date: Wed, 4 Feb 2026 16:20:40 +0100 Subject: [PATCH 2/4] Address review comment --- .../providers/amazon/aws/auth_manager/aws_auth_manager.py | 1 + .../src/airflow/providers/fab/auth_manager/fab_auth_manager.py | 1 + .../providers/keycloak/auth_manager/keycloak_auth_manager.py | 1 + 3 files changed, 3 insertions(+) diff --git a/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py b/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py index 4d570760462cf..9d3f2fabe9e52 100644 --- a/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py +++ b/providers/amazon/src/airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py @@ -160,6 +160,7 @@ def is_authorized_dag( def is_authorized_backfill( self, *, method: ResourceMethod, user: AwsAuthManagerUser, details: BackfillDetails | None = None ) -> bool: + # Method can be removed once the min Airflow version is >= 3.2.0. warnings.warn( "Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", AirflowProviderDeprecationWarning, diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py index 0ffcd6d0dcb3f..14f7eb1f41f18 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager.py @@ -390,6 +390,7 @@ def is_authorized_backfill( user: User, details: BackfillDetails | None = None, ) -> bool: + # Method can be removed once the min Airflow version is >= 3.2.0. warnings.warn( "Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", AirflowProviderDeprecationWarning, diff --git a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py index 1daca0ffd7429..746a5a428cbb0 100644 --- a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py +++ b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py @@ -222,6 +222,7 @@ def is_authorized_dag( def is_authorized_backfill( self, *, method: ResourceMethod, user: KeycloakAuthManagerUser, details: BackfillDetails | None = None ) -> bool: + # Method can be removed once the min Airflow version is >= 3.2.0. warnings.warn( "Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", AirflowProviderDeprecationWarning, From 8fd93d787131d67d8e24cada69a5677cb7d9ce84 Mon Sep 17 00:00:00 2001 From: pierrejeambrun Date: Wed, 4 Feb 2026 18:02:36 +0100 Subject: [PATCH 3/4] Fix CI --- .../fab/auth_manager/test_fab_auth_manager.py | 21 ++++++--- .../test_keycloak_auth_manager.py | 43 ++++++++++++++++--- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/providers/fab/tests/unit/fab/auth_manager/test_fab_auth_manager.py b/providers/fab/tests/unit/fab/auth_manager/test_fab_auth_manager.py index 0170d14923f99..d9c7bbc9f915e 100644 --- a/providers/fab/tests/unit/fab/auth_manager/test_fab_auth_manager.py +++ b/providers/fab/tests/unit/fab/auth_manager/test_fab_auth_manager.py @@ -17,7 +17,7 @@ from __future__ import annotations import time -from contextlib import contextmanager, suppress +from contextlib import ExitStack, contextmanager, suppress from itertools import chain from typing import TYPE_CHECKING from unittest import mock @@ -29,7 +29,7 @@ from airflow.api_fastapi.app import AUTH_MANAGER_FASTAPI_APP_PREFIX from airflow.api_fastapi.common.types import MenuItem -from airflow.exceptions import AirflowConfigException +from airflow.exceptions import AirflowConfigException, AirflowProviderDeprecationWarning from airflow.providers.fab.www.app import create_app from airflow.providers.fab.www.utils import get_fab_auth_manager from airflow.providers.standard.operators.empty import EmptyOperator @@ -328,10 +328,19 @@ def test_create_token_wrong_values(self, username, password, auth_manager_with_a def test_is_authorized(self, api_name, method, user_permissions, expected_result, auth_manager): user = Mock() user.perms = user_permissions - result = getattr(auth_manager, api_name)( - method=method, - user=user, - ) + + with ExitStack() as stack: + if api_name == "is_authorized_backfill": + stack.enter_context( + pytest.warns( + AirflowProviderDeprecationWarning, + match="Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", + ) + ) + result = getattr(auth_manager, api_name)( + method=method, + user=user, + ) assert result == expected_result @pytest.mark.parametrize( diff --git a/providers/keycloak/tests/unit/keycloak/auth_manager/test_keycloak_auth_manager.py b/providers/keycloak/tests/unit/keycloak/auth_manager/test_keycloak_auth_manager.py index 27747f506961b..faf954eb2eda8 100644 --- a/providers/keycloak/tests/unit/keycloak/auth_manager/test_keycloak_auth_manager.py +++ b/providers/keycloak/tests/unit/keycloak/auth_manager/test_keycloak_auth_manager.py @@ -17,6 +17,7 @@ from __future__ import annotations import json +from contextlib import ExitStack from unittest.mock import Mock, patch import pytest @@ -36,6 +37,7 @@ VariableDetails, ) from airflow.api_fastapi.common.types import MenuItem +from airflow.exceptions import AirflowProviderDeprecationWarning from airflow.providers.common.compat.sdk import AirflowException from airflow.providers.keycloak.auth_manager.constants import ( CONF_CLIENT_ID_KEY, @@ -263,7 +265,16 @@ def test_is_authorized( mock_response.status_code = status_code auth_manager.http_session.post = Mock(return_value=mock_response) - result = getattr(auth_manager, function)(method=method, user=user, details=details) + with ExitStack() as stack: + if function == "is_authorized_backfill": + stack.enter_context( + pytest.warns( + AirflowProviderDeprecationWarning, + match="Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", + ) + ) + + result = getattr(auth_manager, function)(method=method, user=user, details=details) token_url = auth_manager._get_token_url("server_url", "realm") payload = auth_manager._get_payload("client_id", permission, attributes) @@ -291,10 +302,19 @@ def test_is_authorized_failure(self, function, auth_manager, user): resp.status_code = 500 auth_manager.http_session.post = Mock(return_value=resp) - with pytest.raises(AirflowException) as e: - getattr(auth_manager, function)(method="GET", user=user) + with ExitStack() as stack: + if function == "is_authorized_backfill": + stack.enter_context( + pytest.warns( + AirflowProviderDeprecationWarning, + match="Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", + ) + ) + + with pytest.raises(AirflowException) as e: + getattr(auth_manager, function)(method="GET", user=user) - assert "Unexpected error" in str(e.value) + assert "Unexpected error" in str(e.value) @pytest.mark.parametrize( "function", @@ -315,10 +335,19 @@ def test_is_authorized_invalid_request(self, function, auth_manager, user): resp.text = json.dumps({"error": "invalid_scope", "error_description": "Invalid scopes: GET"}) auth_manager.http_session.post = Mock(return_value=resp) - with pytest.raises(AirflowException) as e: - getattr(auth_manager, function)(method="GET", user=user) + with ExitStack() as stack: + if function == "is_authorized_backfill": + stack.enter_context( + pytest.warns( + AirflowProviderDeprecationWarning, + match="Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", + ) + ) + + with pytest.raises(AirflowException) as e: + getattr(auth_manager, function)(method="GET", user=user) - assert "Request not recognized by Keycloak. invalid_scope. Invalid scopes: GET" in str(e.value) + assert "Request not recognized by Keycloak. invalid_scope. Invalid scopes: GET" in str(e.value) @pytest.mark.parametrize( ("method", "access_entity", "details", "permission", "attributes"), From 9c88a0de7cb75fa7ac50b856e86df9bfddb99cf7 Mon Sep 17 00:00:00 2001 From: pierrejeambrun Date: Thu, 5 Feb 2026 00:09:34 +0100 Subject: [PATCH 4/4] Fix CI --- .../aws/auth_manager/test_aws_auth_manager.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/providers/amazon/tests/unit/amazon/aws/auth_manager/test_aws_auth_manager.py b/providers/amazon/tests/unit/amazon/aws/auth_manager/test_aws_auth_manager.py index 8ee6fa8470b89..8e0ff06c18584 100644 --- a/providers/amazon/tests/unit/amazon/aws/auth_manager/test_aws_auth_manager.py +++ b/providers/amazon/tests/unit/amazon/aws/auth_manager/test_aws_auth_manager.py @@ -16,11 +16,14 @@ # under the License. from __future__ import annotations +from contextlib import ExitStack from typing import TYPE_CHECKING from unittest.mock import ANY, Mock, patch import pytest +from airflow.exceptions import AirflowProviderDeprecationWarning + from tests_common.test_utils.version_compat import AIRFLOW_V_3_0_PLUS if not AIRFLOW_V_3_0_PLUS: @@ -226,8 +229,16 @@ def test_is_authorized_backfill( is_authorized = Mock(return_value=True) mock_avp_facade.is_authorized = is_authorized - method: ResourceMethod = "GET" - result = auth_manager.is_authorized_backfill(method=method, details=details, user=user) + with ExitStack() as stack: + stack.enter_context( + pytest.warns( + AirflowProviderDeprecationWarning, + match="Use ``is_authorized_dag`` on ``DagAccessEntity.RUN`` instead for a dag level access control.", + ) + ) + + method: ResourceMethod = "GET" + result = auth_manager.is_authorized_backfill(method=method, details=details, user=user) is_authorized.assert_called_once_with( method=method, entity_type=AvpEntities.BACKFILL, user=expected_user, entity_id=expected_entity_id