shahar1 commented on code in PR #76702:
URL: https://github.com/apache/airflow/pull/76702#discussion_r4563979435


##########
providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py:
##########
@@ -787,6 +796,7 @@ def create_team_command(args):
     _attach_team_permissions(client, client_uuid, team, _dry_run=args.dry_run)
     _attach_team_menu_permissions(client, client_uuid, team, 
_dry_run=args.dry_run)
     _attach_superadmin_permissions(client, client_uuid, team, 
_dry_run=args.dry_run)
+    _attach_readonly_permissions(client, client_uuid, team, 
_dry_run=args.dry_run)

Review Comment:
   **major** — On a client provisioned with `create-all --teams ...`, the 
global `ReadOnly` permission does not exist: the teams branch of 
`_get_permissions_to_create` creates `ReadOnly-<team>` only. This call then 
reaches `_attach_policy_to_scope_permission(permission_name="ReadOnly", ...)`, 
which raises `ValueError("Permission 'ReadOnly' not found.")`, so `create-team` 
stops here.
   
   `test_create_team_command` mocks `_attach_policy_to_scope_permission`, so CI 
does not see it. A dedicated permission created in the teams branch (see the 
next comment) would avoid depending on `ReadOnly` at all.



##########
providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py:
##########
@@ -1022,6 +1032,52 @@ def _attach_superadmin_permissions(
     )
 
 
+def _attach_readonly_permissions(
+    client: KeycloakAdmin, client_uuid: str, team: str, *, _dry_run: bool = 
False
+) -> None:
+    team_scoped_resources = [f"{resource}:{team}" for resource in 
sorted(TEAM_SCOPED_RESOURCE_NAMES)]
+    _attach_policy_to_scope_permission(
+        client,
+        client_uuid,
+        permission_name="ReadOnly",
+        policy_name=_role_policy_name(READ_ONLY_ROLE_NAME),
+        scope_names=["GET", "LIST", "MENU"],
+        resource_names=team_scoped_resources,
+        decision_strategy="AFFIRMATIVE",
+        _dry_run=_dry_run,
+    )
+    _attach_policy_to_scope_permission(
+        client,
+        client_uuid,
+        permission_name="ViewAccess",
+        policy_name=_role_policy_name(READ_ONLY_ROLE_NAME),
+        scope_names=["GET"],
+        resource_names=[KeycloakResource.VIEW.value],
+        decision_strategy="AFFIRMATIVE",
+        _dry_run=_dry_run,
+    )
+    _attach_policy_to_scope_permission(
+        client,
+        client_uuid,
+        permission_name="AdminViewAccess",
+        policy_name=_role_policy_name(READ_ONLY_ROLE_NAME),

Review Comment:
   **major** — `AdminView#GET` is not read-only. In multi-team mode 
`is_authorized_view` checks `REPARSE_ALL` against `AdminView` 
(`ADMIN_ACCESS_VIEW_NAMES`), and `requires_access_dag_from_file_token` uses 
`REPARSE_ALL` to authorize reparsing a file with no registered Dag. With this 
attachment a `GlobalReadOnly` user can queue reparse requests.
   
   The docs also say `AdminView` is granted "to ``SuperAdmin`` only" 
(`docs/auth-manager/manage/permissions.rst`). Either drop this attachment, or 
split reparse from the read views before granting it.
   
   *Flagged by the adversarial reviewer (copilot); cross-checked.*



##########
providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py:
##########
@@ -258,14 +261,14 @@ def _ensure_multi_team_enabled(*, teams: list[str], 
command_name: str) -> None:
 
 
 def _ensure_default_role_policies(client: KeycloakAdmin, client_uuid: str, *, 
_dry_run: bool = False) -> None:
-    for role_name in (*TEAM_ROLE_NAMES, SUPER_ADMIN_ROLE_NAME):
+    for role_name in (*TEAM_ROLE_NAMES, READ_ONLY_ROLE_NAME, 
SUPER_ADMIN_ROLE_NAME):

Review Comment:
   **minor** — `_ensure_role_policy` looks the role up with `_get_role_id` and 
never creates it, so `create-permissions` / `create-all` now fail with 
`KeycloakGetError` on any client, including non-team ones, until a 
`GlobalReadOnly` role is added in Keycloak. Existing multi-team clients also 
need `create-permissions --teams` re-run before `create-team`, otherwise it 
fails with `Policy 'Allow-GlobalReadOnly' not found.`
   
   `providers/AGENTS.md`:
   > Keep `provider.yaml` metadata, docs, and tests in sync.
   
   > When a provider change needs a user-visible note (typically a breaking 
change or important behavior change that warrants explanation), update the 
provider's `docs/changelog.rst` directly in the same PR, just below the 
`Changelog` header
   
   Please add the upgrade note to `providers/keycloak/docs/changelog.rst` and 
describe the role in `docs/auth-manager/manage/permissions.rst`, next to 
`SuperAdmin`.
   
   *Flagged by both the primary and adversarial reviewers.*



##########
providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py:
##########
@@ -324,12 +327,18 @@ def _update_read_only_permission_resources(
         return
 
     permission_id = match["id"]
-    scopes = client.get_client_authz_scopes(client_uuid)
-    resources = client.get_client_authz_resources(client_uuid)
-    scope_ids = [s["id"] for s in scopes if s["name"] in ["GET", "MENU", 
"LIST"]]
-    resource_names = TEAM_SCOPED_RESOURCE_NAMES | GLOBAL_SCOPED_RESOURCE_NAMES
-    resource_ids = [r["_id"] for r in resources if r["name"] in resource_names]
+    scope_ids = [
+        s["id"] for s in client.get_client_authz_scopes(client_uuid) if 
s["name"] in ["GET", "MENU", "LIST"]
+    ]
+    resource_ids = [
+        r["_id"]
+        for r in client.get_client_authz_resources(client_uuid)
+        if r["name"] in TEAM_SCOPED_RESOURCE_NAMES | 
GLOBAL_SCOPED_RESOURCE_NAMES

Review Comment:
   **major** — This filter matches exact names (`Dag`, `Connection`, ...), so 
the `Dag:<team>` resources that `_attach_readonly_permissions` just put on 
`ReadOnly` are removed again by this update. After `create-team`, 
`GlobalReadOnly` has no `GET` on any team resource. The earlier thread on this 
was resolved as fixed, but this filter and 
`test_update_read_only_permission_resources_excludes_team_resources` still 
exclude them.
   
   Simply adding the team resources here would be unsafe: on clients that were 
first set up without `--teams`, `ReadOnly` also carries the plain 
`Allow-Viewer` / `Allow-User` / `Allow-Op` / `Allow-Admin` role policies under 
`AFFIRMATIVE`, so every Viewer would get read access to every team. 
`_attach_policy_to_scope_permission` also replaces the resource list on each 
call, so a second team would overwrite the first.
   
   Suggestion: a separate permission (for example `GlobalReadOnly`) that holds 
only `Allow-GlobalReadOnly`, with its resources rebuilt across all teams the 
way `_update_admin_permission_resources` does for `Admin`.



##########
providers/keycloak/tests/unit/keycloak/auth_manager/cli/test_commands.py:
##########
@@ -757,6 +760,32 @@ def test_create_team_command(
             decision_strategy="AFFIRMATIVE",
             _dry_run=False,
         )
+        mock_attach_policy.assert_any_call(

Review Comment:
   **minor** — These assertions check that the mocked attach was called, not 
what `ReadOnly` ends up containing once `create-team` finishes, so they pass 
while the two problems in `commands.py` (missing permission, resources removed 
again) go unnoticed.
   
   `AGENTS.md`, Testing Standards:
   > Every changed or added behaviour must have a test; every test must fail 
without the PR's change.
   
   A test that runs `create-team` with only the Keycloak client mocked and 
asserts the final `update_client_authz_scope_permission` payload for the 
read-only permission would cover it.



##########
providers/keycloak/src/airflow/providers/keycloak/auth_manager/cli/commands.py:
##########
@@ -1022,6 +1032,52 @@ def _attach_superadmin_permissions(
     )
 
 
+def _attach_readonly_permissions(
+    client: KeycloakAdmin, client_uuid: str, team: str, *, _dry_run: bool = 
False
+) -> None:
+    team_scoped_resources = [f"{resource}:{team}" for resource in 
sorted(TEAM_SCOPED_RESOURCE_NAMES)]

Review Comment:
   **minor** — The description says the role "should remain equivalent to the 
'Viewer' role for each team". Team Viewer gets `GET`/`LIST` on `Dag:<team>` and 
`Team:<team>` and the `TEAM_MENU_ITEMS` menu entries. This grants `GET` on 
`Connection`, `Pool` and `Variable` of every team, every `MenuItem`, and 
`AdminView`. Is the wider scope intended? If so, the description and docs 
should say so; if not, `team_readable_resources` and `TEAM_MENU_ITEMS` are the 
Viewer-equivalent sets.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to