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

potiuk 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 3c37b4794f8 Fix Keycloak access for individual roles (#70906)
3c37b4794f8 is described below

commit 3c37b4794f8ffb3dce83622561932c80551bf9ff
Author: D.H.KIM <[email protected]>
AuthorDate: Sat Aug 8 11:19:31 2026 +0900

    Fix Keycloak access for individual roles (#70906)
    
    * Fix Keycloak team Dag access for non-admin roles
    
    Team ReadOnly permissions combined mutually exclusive role policies with an 
UNANIMOUS strategy, preventing non-admin team members from reading team Dags.
    
    * Fix Keycloak global admin access for individual roles
    
    Admin and SuperAdmin are alternative global administrator roles, but the 
current policy combination requires users to hold both roles.\n\nExisting 
Keycloak objects are external state, so operators need clear upgrade guidance 
to apply the corrected policy.
    
    * Preserve global Admin access when creating teams
    
    Deployments that enable multi-team mode later must keep global access 
independent of whether both Admin and SuperAdmin policies are attached.
---
 providers/keycloak/docs/changelog.rst              | 10 +++++
 .../keycloak/auth_manager/cli/commands.py          |  4 ++
 .../keycloak/auth_manager/cli/test_commands.py     | 50 ++++++++++------------
 3 files changed, 37 insertions(+), 27 deletions(-)

diff --git a/providers/keycloak/docs/changelog.rst 
b/providers/keycloak/docs/changelog.rst
index 35f18bbee0b..7f1ff247328 100644
--- a/providers/keycloak/docs/changelog.rst
+++ b/providers/keycloak/docs/changelog.rst
@@ -25,6 +25,16 @@
 Changelog
 ---------
 
+.. note::
+    Upgrading the provider does not update existing Keycloak permissions. For 
each existing team,
+    run ``airflow keycloak-auth-manager create-team <team>`` again to update 
the team ReadOnly
+    permission. For non-team installations, run
+    ``airflow keycloak-auth-manager create-permissions`` again without 
``--teams`` to update the
+    global Admin permission.
+
+    Manually added policies attached to these permissions will also be 
evaluated under the
+    ``AFFIRMATIVE`` strategy after the update.
+
 0.9.0
 .....
 
diff --git 
a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py
 
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py
index 6ead332155b..adb9aac6ff8 100644
--- 
a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py
+++ 
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py
@@ -301,6 +301,7 @@ def _attach_default_role_permissions(
             policy_name=_role_policy_name(role_name),
             scope_names=_get_extended_resource_methods() + ["LIST"],
             resource_names=[],
+            decision_strategy="AFFIRMATIVE",
             _dry_run=_dry_run,
         )
 
@@ -792,6 +793,7 @@ def _attach_team_permissions(
         policy_name=_team_role_policy_name(team, "Viewer"),
         scope_names=["GET", "LIST"],
         resource_names=team_readable_resources,
+        decision_strategy="AFFIRMATIVE",
         _dry_run=_dry_run,
     )
     for role_name in ("User", "Op", "Admin"):
@@ -802,6 +804,7 @@ def _attach_team_permissions(
             policy_name=_team_role_policy_name(team, role_name),
             scope_names=["GET", "LIST"],
             resource_names=team_readable_resources,
+            decision_strategy="AFFIRMATIVE",
             _dry_run=_dry_run,
         )
     _attach_policy_to_scope_permission(
@@ -926,6 +929,7 @@ def _attach_superadmin_permissions(
         policy_name=_role_policy_name(SUPER_ADMIN_ROLE_NAME),
         scope_names=_get_extended_resource_methods() + ["LIST"],
         resource_names=team_scoped_resources,
+        decision_strategy="AFFIRMATIVE",
         _dry_run=_dry_run,
     )
     _attach_policy_to_scope_permission(
diff --git 
a/providers/keycloak/tests/unit/keycloak/auth_manager/cli/test_commands.py 
b/providers/keycloak/tests/unit/keycloak/auth_manager/cli/test_commands.py
index 62c61f95f72..450996ad0a4 100644
--- a/providers/keycloak/tests/unit/keycloak/auth_manager/cli/test_commands.py
+++ b/providers/keycloak/tests/unit/keycloak/auth_manager/cli/test_commands.py
@@ -472,24 +472,17 @@ class TestCommands:
                 decision_strategy="AFFIRMATIVE",
                 _dry_run=False,
             )
-        mock_attach_scope_policy.assert_any_call(
-            client,
-            "test-id",
-            permission_name="Admin",
-            policy_name="Allow-Admin",
-            scope_names=_get_extended_resource_methods() + ["LIST"],
-            resource_names=[],
-            _dry_run=False,
-        )
-        mock_attach_scope_policy.assert_any_call(
-            client,
-            "test-id",
-            permission_name="Admin",
-            policy_name="Allow-SuperAdmin",
-            scope_names=_get_extended_resource_methods() + ["LIST"],
-            resource_names=[],
-            _dry_run=False,
-        )
+        for role_name in ("Admin", SUPER_ADMIN_ROLE_NAME):
+            mock_attach_scope_policy.assert_any_call(
+                client,
+                "test-id",
+                permission_name="Admin",
+                policy_name=f"Allow-{role_name}",
+                scope_names=_get_extended_resource_methods() + ["LIST"],
+                resource_names=[],
+                decision_strategy="AFFIRMATIVE",
+                _dry_run=False,
+            )
         mock_attach_resource_policy.assert_any_call(
             client,
             "test-id",
@@ -567,15 +560,17 @@ class TestCommands:
         mock_update_admin_permission_resources.assert_called_once_with(client, 
"test-id", _dry_run=False)
         mock_ensure_group_policy.assert_called_once_with(client, "test-id", 
"team-a", _dry_run=False)
         assert mock_ensure_aggregate_policy.call_count == 4
-        mock_attach_policy.assert_any_call(
-            client,
-            "test-id",
-            permission_name="ReadOnly-team-a",
-            policy_name="Allow-Viewer-team-a",
-            scope_names=["GET", "LIST"],
-            resource_names=["Dag:team-a", "Team:team-a"],
-            _dry_run=False,
-        )
+        for role_name in TEAM_ROLE_NAMES:
+            mock_attach_policy.assert_any_call(
+                client,
+                "test-id",
+                permission_name="ReadOnly-team-a",
+                policy_name=f"Allow-{role_name}-team-a",
+                scope_names=["GET", "LIST"],
+                resource_names=["Dag:team-a", "Team:team-a"],
+                decision_strategy="AFFIRMATIVE",
+                _dry_run=False,
+            )
         mock_attach_policy.assert_any_call(
             client,
             "test-id",
@@ -644,6 +639,7 @@ class TestCommands:
                 "Team:team-a",
                 "Variable:team-a",
             ],
+            decision_strategy="AFFIRMATIVE",
             _dry_run=False,
         )
         mock_attach_policy.assert_any_call(

Reply via email to