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 2c98ac79d93 Return 403 when Keycloak UMA ticket grant returns
invalid_grant (#73234)
2c98ac79d93 is described below
commit 2c98ac79d93b340b664dc8d195d3a633c7b1104b
Author: Shashank Mittal <[email protected]>
AuthorDate: Mon Sep 21 19:00:36 2026 +0530
Return 403 when Keycloak UMA ticket grant returns invalid_grant (#73234)
Keycloak's uma-ticket grant often returns HTTP 400 invalid_grant when the
nested access token used as bearer is expired. Treat that the same as 401
so the API returns 403 instead of raising AirflowException as HTTP 500.
---
.../keycloak/auth_manager/keycloak_auth_manager.py | 8 ++++++++
.../auth_manager/test_keycloak_auth_manager.py | 20 ++++++++++++++++++++
2 files changed, 28 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 d945b4da470..1b8e6d7e630 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
@@ -523,6 +523,11 @@ class
KeycloakAuthManager(BaseAuthManager[KeycloakAuthManagerUser]):
return False
if resp.status_code == 400:
error = json.loads(resp.text)
+ # Keycloak's uma-ticket grant often returns 400 invalid_grant (not
401)
+ # when the nested access token used as bearer is expired or
invalid.
+ if error.get("error") == "invalid_grant":
+ log.debug("Received invalid_grant from Keycloak: %s",
resp.text)
+ return False
if is_team_resource and error.get("error") == "invalid_resource":
# filter_authorized_dag_ids will return this error if team
resources have not been added to the Keycloak Client.
log.warning(
@@ -821,6 +826,9 @@ class
KeycloakAuthManager(BaseAuthManager[KeycloakAuthManagerUser]):
return set()
if resp.status_code == 400:
error = json.loads(resp.text)
+ if error.get("error") == "invalid_grant":
+ log.debug("Received invalid_grant from Keycloak: %s",
resp.text)
+ return set()
raise AirflowException(
f"Request not recognized by Keycloak. {error.get('error')}.
{error.get('error_description')}"
)
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 e52b0c24019..d4d3a192887 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
@@ -650,6 +650,16 @@ class TestKeycloakAuthManager:
assert "Keycloak authorization resource is missing; denying access" in
caplog.text
assert "Resource with id [Dag:team-a] does not exist." in caplog.text
+ def test_is_authorized_invalid_grant(self, auth_manager, user):
+ resp = Mock()
+ resp.status_code = 400
+ resp.text = '{"error": "invalid_grant", "error_description": "Invalid
bearer token"}'
+ auth_manager.http_session.post = Mock(return_value=resp)
+
+ result = auth_manager.is_authorized_dag(method="GET",
details=DagDetails(id="dag_0"), user=user)
+
+ assert result is False
+
@pytest.mark.parametrize(
"function",
[
@@ -1102,6 +1112,16 @@ class TestKeycloakAuthManager:
token_url, data=payload, headers=headers, timeout=5
)
+ def test_filter_authorized_menu_items_invalid_grant(self, auth_manager,
user):
+ resp = Mock()
+ resp.status_code = 400
+ resp.text = '{"error": "invalid_grant", "error_description": "Invalid
bearer token"}'
+ auth_manager.http_session.post = Mock(return_value=resp)
+
+ result = auth_manager.filter_authorized_menu_items([MenuItem.ASSETS,
MenuItem.CONNECTIONS], user=user)
+
+ assert result == []
+
def test_get_cli_commands_return_cli_commands(self, auth_manager):
assert len(auth_manager.get_cli_commands()) == 1