You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by vi...@apache.org on 2026/03/02 20:33:47 UTC
(airflow) branch main updated: feat(keycloak): add method to retrieve teams from Keycloak as resources (#62715)
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 c6d8e167bd5 feat(keycloak): add method to retrieve teams from Keycloak as resources (#62715)
c6d8e167bd5 is described below
commit c6d8e167bd5693f61a7b8e19b706204b89b0ec67
Author: Mathieu Monet <60...@users.noreply.github.com>
AuthorDate: Mon Mar 2 21:33:40 2026 +0100
feat(keycloak): add method to retrieve teams from Keycloak as resources (#62715)
---
.../keycloak/auth_manager/keycloak_auth_manager.py | 18 +++++++++++++++
.../auth_manager/test_keycloak_auth_manager.py | 27 ++++++++++++++++++++++
2 files changed, 45 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 11f2c55b0f7..bc398020844 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
@@ -471,6 +471,24 @@ class KeycloakAuthManager(BaseAuthManager[KeycloakAuthManagerUser]):
)
raise AirflowException(f"Unexpected error: {resp.status_code} - {resp.text}")
+ def _get_teams(self) -> set[str]:
+ realm = conf.get(CONF_SECTION_NAME, CONF_REALM_KEY)
+ server_url = conf.get(CONF_SECTION_NAME, CONF_SERVER_URL_KEY)
+
+ pat = self.get_keycloak_client().token(grant_type="client_credentials")["access_token"]
+
+ prefix = f"{KeycloakResource.TEAM.value}:"
+ resource_url = f"{server_url.rstrip('/')}/realms/{realm}/authz/protection/resource_set"
+ resources_resp = self.http_session.get(
+ resource_url,
+ params={"name": prefix, "matchingUri": "false", "max": "-1", "deep": "true"},
+ headers={"Authorization": f"Bearer {pat}"},
+ timeout=5,
+ )
+ resources_resp.raise_for_status()
+
+ return {r["name"][len(prefix) :] for r in resources_resp.json() if r["name"].startswith(prefix)}
+
@staticmethod
def _get_token_url(server_url, realm):
# Normalize server_url to avoid double slashes (required for Keycloak 26.4+ strict path validation).
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 4610b45bf63..b1c28714f6b 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
@@ -933,3 +933,30 @@ class TestKeycloakAuthManager:
"""Test that _get_token_url normalizes server_url by stripping trailing slashes."""
token_url = auth_manager._get_token_url(server_url, "myrealm")
assert token_url == expected_url
+
+ @pytest.mark.skipif(not AIRFLOW_V_3_2_PLUS, reason="Team not available before Airflow 3.2.0")
+ @patch(
+ "airflow.providers.keycloak.auth_manager.keycloak_auth_manager.KeycloakAuthManager.get_keycloak_client"
+ )
+ def test_get_teams(self, mock_get_keycloak_client, auth_manager_multi_team):
+ """_get_teams fetches Team: resources from Keycloak and returns team names."""
+ mock_get_keycloak_client.return_value.token.return_value = {"access_token": "pat-token"}
+
+ mock_response = Mock()
+ mock_response.json.return_value = [
+ {"name": "Team:team-a", "type": "urn:airflow:resource"},
+ {"name": "Team:team-b", "type": "urn:airflow:resource"},
+ {"name": "Connection", "type": "urn:airflow:resource"}, # should be ignored
+ ]
+ mock_response.raise_for_status = Mock()
+ auth_manager_multi_team.http_session.get = Mock(return_value=mock_response)
+
+ result = auth_manager_multi_team._get_teams()
+
+ assert result == {"team-a", "team-b"}
+ auth_manager_multi_team.http_session.get.assert_called_once_with(
+ "server_url/realms/realm/authz/protection/resource_set",
+ params={"name": "Team:", "matchingUri": "false", "max": "-1", "deep": "true"},
+ headers={"Authorization": "Bearer pat-token"},
+ timeout=5,
+ )