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 b4114239a89 Handle missing Keycloak resources as access denied (#68951)
b4114239a89 is described below

commit b4114239a895883f398c8d61e6c016c85c3bbf47
Author: Arnav <[email protected]>
AuthorDate: Fri Jun 26 00:47:21 2026 +0530

    Handle missing Keycloak resources as access denied (#68951)
---
 .../keycloak/auth_manager/keycloak_auth_manager.py | 10 ++++++
 .../auth_manager/test_keycloak_auth_manager.py     | 37 ++++++++++++++++++++++
 2 files changed, 47 insertions(+)

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 a8cd683ef46..545f7803994 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
@@ -78,6 +78,13 @@ if TYPE_CHECKING:
 log = logging.getLogger(__name__)
 
 RESOURCE_ID_ATTRIBUTE_NAME = "resource_id"
+KEYCLOAK_RESOURCE_NOT_FOUND_ERROR = "resource not found:"
+
+
+def _is_missing_keycloak_resource_response(status_code: int, text: Any) -> 
bool:
+    return status_code == 500 and isinstance(text, str) and 
KEYCLOAK_RESOURCE_NOT_FOUND_ERROR in text.lower()
+
+
 TEAM_SCOPED_RESOURCES = frozenset(
     {
         KeycloakResource.CONNECTION,
@@ -434,6 +441,9 @@ class 
KeycloakAuthManager(BaseAuthManager[KeycloakAuthManagerUser]):
             raise AirflowException(
                 f"Request not recognized by Keycloak. {error.get('error')}. 
{error.get('error_description')}"
             )
+        if _is_missing_keycloak_resource_response(resp.status_code, resp.text):
+            log.warning("Keycloak authorization resource is missing; denying 
access. Response: %s", resp.text)
+            return False
         raise AirflowException(f"Unexpected error: {resp.status_code} - 
{resp.text}")
 
     def filter_authorized_dag_ids(
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 9c8ed9dd5b6..347e9944e5e 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
@@ -386,6 +386,19 @@ class TestKeycloakAuthManager:
 
             assert "Unexpected error" in str(e.value)
 
+    def test_is_authorized_missing_keycloak_resource(self, auth_manager, user, 
caplog):
+        resp = Mock()
+        resp.status_code = 500
+        resp.text = "resource not found: Dag:team-a"
+        auth_manager.http_session.post = Mock(return_value=resp)
+        caplog.set_level("WARNING", 
logger="airflow.providers.keycloak.auth_manager.keycloak_auth_manager")
+
+        result = auth_manager.is_authorized_dag(method="GET", 
details=DagDetails(id="dag_0"), user=user)
+
+        assert result is False
+        assert "Keycloak authorization resource is missing; denying access" in 
caplog.text
+        assert "resource not found: Dag:team-a" in caplog.text
+
     @pytest.mark.parametrize(
         "function",
         [
@@ -652,6 +665,30 @@ class TestKeycloakAuthManager:
         mock_is_authorized.assert_called_once()
         assert result == {"dag-a"}
 
+    @pytest.mark.skipif(not AIRFLOW_V_3_2_PLUS, reason="team_name not 
supported before Airflow 3.2.0")
+    def test_filter_authorized_dag_ids_missing_keycloak_resource(self, 
auth_manager_multi_team, user, caplog):
+        def post_response(*_, data, **__):
+            claims = json.loads(base64.b64decode(data["claim_token"]).decode())
+            dag_id = claims[RESOURCE_ID_ATTRIBUTE_NAME][0]
+            response = Mock()
+            if dag_id == "dag-missing":
+                response.status_code = 500
+                response.text = "resource not found: Dag:team-a"
+            else:
+                response.status_code = 200
+            return response
+
+        auth_manager_multi_team.http_session.post = 
Mock(side_effect=post_response)
+        caplog.set_level("WARNING", 
logger="airflow.providers.keycloak.auth_manager.keycloak_auth_manager")
+
+        result = auth_manager_multi_team.filter_authorized_dag_ids(
+            dag_ids={"dag-allowed", "dag-missing"}, user=user, 
team_name="team-a"
+        )
+
+        assert result == {"dag-allowed"}
+        assert auth_manager_multi_team.http_session.post.call_count == 2
+        assert "Keycloak authorization resource is missing; denying access" in 
caplog.text
+
     @pytest.mark.skipif(not AIRFLOW_V_3_2_PLUS, reason="team_name not 
supported before Airflow 3.2.0")
     @patch.object(KeycloakAuthManager, "is_authorized_pool", 
return_value=False)
     def test_filter_authorized_pools_no_team_returns_empty(

Reply via email to