Fix IDOR: Add permission checks to file ancestry endpoints (#14725)

Close #14292

## Issue

File ancestry endpoints return folder metadata without validating tenant
permissions, allowing any authenticated user to query arbitrary
`file_id` values across tenant boundaries.

## Affected Endpoints
- `GET /v1/file/parent_folder?file_id={file_id}`
- `GET /v1/file/all_parent_folder?file_id={file_id}`  
- `GET /api/v1/files/{id}/ancestors`

## Root Cause

These endpoints **skip the permission check** that other file operations
(Delete, Download, Move) perform.

## Expected Permission Check

All file operations should follow this 3-step validation:

- Check file.tenant_id
- Check if user_id belongs to this tenant (via user_tenant join table)
- Check KB permission type (team permission)


**Code reference:** This is implemented in `checkFileTeamPermission()`
and used by Delete/Download/Move, but **missing** from
GetParentFolder/GetAllParentFolders.

## Reproduction

```bash
# User B (tenant: BBB) accessing User A's file (tenant: AAA)
curl -H "Authorization: Bearer USER_B_TOKEN" \
  "http://localhost:9384/v1/file/parent_folder?file_id=AAA_FILE_123"

# Result: Returns User A's folder metadata 
# Expected: "No authorization." 
Fix
Pass userID from handler to service and call checkFileTeamPermission() — same as Download/Delete/Move handlers.

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
akie
2026-05-09 16:03:23 +08:00
committed by GitHub
parent 6465753968
commit c11650bb4c
5 changed files with 54 additions and 24 deletions

View File

@@ -335,7 +335,7 @@ async def parent_folder(tenant_id: str = None, file_id: str = None):
description: Parent folder information.
"""
try:
success, result = file_api_service.get_parent_folder(file_id)
success, result = file_api_service.get_parent_folder(file_id, user_id=tenant_id)
if success:
return get_result(data=result)
else:
@@ -366,7 +366,7 @@ async def ancestors(tenant_id: str = None, file_id: str = None):
description: List of ancestor folders.
"""
try:
success, result = file_api_service.get_all_parent_folders(file_id)
success, result = file_api_service.get_all_parent_folders(file_id, user_id=tenant_id)
if success:
return get_result(data=result)
else:

View File

@@ -174,32 +174,46 @@ def list_files(tenant_id: str, args: dict):
def get_parent_folder(file_id: str):
def get_parent_folder(file_id: str, user_id: str = None):
"""
Get parent folder of a file.
Get parent folder of a file with permission check.
:param file_id: file ID
:param user_id: user ID for permission validation
:return: (success, result) or (success, error_message)
"""
from api.common.check_team_permission import check_file_team_permission
e, file = FileService.get_by_id(file_id)
if not e:
return False, "Folder not found!"
# Permission check
if user_id and not check_file_team_permission(file, user_id):
return False, "No authorization."
parent_folder = FileService.get_parent_folder(file_id)
return True, {"parent_folder": parent_folder.to_json()}
def get_all_parent_folders(file_id: str):
def get_all_parent_folders(file_id: str, user_id: str = None):
"""
Get all ancestor folders of a file.
Get all ancestor folders of a file with permission check.
:param file_id: file ID
:param user_id: user ID for permission validation
:return: (success, result) or (success, error_message)
"""
from api.common.check_team_permission import check_file_team_permission
e, file = FileService.get_by_id(file_id)
if not e:
return False, "Folder not found!"
# Permission check
if user_id and not check_file_team_permission(file, user_id):
return False, "No authorization."
parent_folders = FileService.get_all_parent_folders(file_id)
return True, {"parent_folders": [pf.to_json() for pf in parent_folders]}