fix(files): Enforce DELETE action permission for file deletion (#4275)

Previously, file deletion only checked READ permission via the
_lookup_file_id() method. This meant any user with READ access to a file
could also delete it, making it impossible to configure read-only file
access.

This change adds an 'action' parameter to fetch_all() and fetch_one() in
AuthorizedSqlStore, defaulting to Action.READ for backward
compatibility. The openai_delete_file() method now passes Action.DELETE,
ensuring proper RBAC enforcement.

With this fix, access policies can now distinguish between Users who can
read/list files but not delete them

Closes: #4274

Signed-off-by: Derek Higgins <derekh@redhat.com>
(cherry picked from commit 4ff0c25c52)

# Conflicts:
#	llama_stack/providers/inline/files/localfs/files.py
#	llama_stack/providers/remote/files/s3/files.py
#	src/llama_stack/providers/remote/files/openai/files.py
This commit is contained in:
Derek Higgins 2025-12-02 17:56:59 +00:00 committed by Mergify
parent 63e2e7534f
commit 4610c29d1e
4 changed files with 278 additions and 7 deletions

View file

@ -136,6 +136,7 @@ class AuthorizedSqlStore:
limit: int | None = None,
order_by: list[tuple[str, Literal["asc", "desc"]]] | None = None,
cursor: tuple[str, str] | None = None,
action: Action = Action.READ,
) -> PaginatedResponse:
"""Fetch all rows with automatic access control filtering."""
access_where = self._build_access_control_where_clause(self.policy)
@ -160,7 +161,7 @@ class AuthorizedSqlStore:
str(record_id), table, User(principal=stored_owner_principal, attributes=stored_access_attrs)
)
if is_action_allowed(self.policy, Action.READ, sql_record, current_user):
if is_action_allowed(self.policy, action, sql_record, current_user):
filtered_rows.append(row)
return PaginatedResponse(
@ -173,6 +174,7 @@ class AuthorizedSqlStore:
table: str,
where: Mapping[str, Any] | None = None,
order_by: list[tuple[str, Literal["asc", "desc"]]] | None = None,
action: Action = Action.READ,
) -> dict[str, Any] | None:
"""Fetch one row with automatic access control checking."""
results = await self.fetch_all(
@ -180,6 +182,7 @@ class AuthorizedSqlStore:
where=where,
limit=1,
order_by=order_by,
action=action,
)
return results.data[0] if results.data else None