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

jasonliu 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 b696a5cee9e Deprecate has access backfill in providers (#61402)
b696a5cee9e is described below

commit b696a5cee9ea983fd20ee1f380d4e0be2c58f356
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Thu Feb 5 05:21:41 2026 +0100

    Deprecate has access backfill in providers (#61402)
    
    * Deprecate 'is_authorized_backfill' in providers
    
    * Address review comment
    
    * Fix CI
    
    * Fix CI
---
 .../amazon/aws/auth_manager/aws_auth_manager.py    |  9 +++++
 .../aws/auth_manager/test_aws_auth_manager.py      | 15 +++++++-
 .../providers/fab/auth_manager/fab_auth_manager.py |  9 ++++-
 .../unit/fab/auth_manager/test_fab_auth_manager.py | 21 ++++++++---
 .../keycloak/auth_manager/keycloak_auth_manager.py |  9 +++++
 .../auth_manager/test_keycloak_auth_manager.py     | 43 ++++++++++++++++++----
 6 files changed, 90 insertions(+), 16 deletions(-)

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 aa23b4f2634..9d3f2fabe9e 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 fastapi import FastAPI
 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,13 @@ class AwsAuthManager(BaseAuthManager[AwsAuthManagerUser]):
     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,
+            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/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 8ee6fa8470b..8e0ff06c185 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 @@ class TestAwsAuthManager:
         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
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 aebd4fa6f14..14f7eb1f41f 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.auth.managers.models.resource_details import (
 )
 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,12 @@ class FabAuthManager(BaseAuthManager[User]):
         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,
+            stacklevel=2,
+        )
         return self._is_authorized(method=method, 
resource_type=RESOURCE_BACKFILL, user=user)
 
     def is_authorized_asset(
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 0170d14923f..d9c7bbc9f91 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 flask_appbuilder.const import AUTH_DB, AUTH_LDAP
 
 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 @@ class TestFabAuthManager:
     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/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py
 
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py
index dc8de7c2595..746a5a428cb 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 @@ from __future__ import annotations
 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 urllib3.util import Retry
 
 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,13 @@ class 
KeycloakAuthManager(BaseAuthManager[KeycloakAuthManagerUser]):
     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,
+            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
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 27747f50696..faf954eb2ed 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 @@ from 
airflow.api_fastapi.auth.managers.models.resource_details import (
     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 @@ class TestKeycloakAuthManager:
         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 @@ class TestKeycloakAuthManager:
         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 @@ class TestKeycloakAuthManager:
         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"),

Reply via email to