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

@@ -155,11 +155,12 @@ func (h *FileHandler) GetRootFolder(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /v1/file/parent_folder [get]
func (h *FileHandler) GetParentFolder(c *gin.Context) {
_, errorCode, errorMessage := GetUser(c)
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
return
}
userID := user.ID
// Get file_id from query
fileID := c.Query("file_id")
@@ -168,8 +169,8 @@ func (h *FileHandler) GetParentFolder(c *gin.Context) {
return
}
// Get parent folder
parentFolder, err := h.fileService.GetParentFolder(fileID)
// Get parent folder with permission check
parentFolder, err := h.fileService.GetParentFolder(userID, fileID)
if err != nil {
jsonError(c, common.CodeServerError, err.Error())
return
@@ -192,11 +193,12 @@ func (h *FileHandler) GetParentFolder(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /v1/file/all_parent_folder [get]
func (h *FileHandler) GetAllParentFolders(c *gin.Context) {
_, errorCode, errorMessage := GetUser(c)
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
return
}
userID := user.ID
// Get file_id from query
fileID := c.Query("file_id")
@@ -205,8 +207,8 @@ func (h *FileHandler) GetAllParentFolders(c *gin.Context) {
return
}
// Get all parent folders
parentFolders, err := h.fileService.GetAllParentFolders(fileID)
// Get all parent folders with permission check
parentFolders, err := h.fileService.GetAllParentFolders(userID, fileID)
if err != nil {
jsonError(c, common.CodeServerError, err.Error())
return
@@ -229,11 +231,12 @@ func (h *FileHandler) GetAllParentFolders(c *gin.Context) {
// @Success 200 {object} map[string]interface{}
// @Router /api/v1/files/{id}/ancestors [get]
func (h *FileHandler) GetFileAncestors(c *gin.Context) {
_, errorCode, errorMessage := GetUser(c)
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
return
}
userID := user.ID
fileID := c.Param("id")
if fileID == "" {
@@ -241,7 +244,8 @@ func (h *FileHandler) GetFileAncestors(c *gin.Context) {
return
}
parentFolders, err := h.fileService.GetAllParentFolders(fileID)
// Get all parent folders with permission check
parentFolders, err := h.fileService.GetAllParentFolders(userID, fileID)
if err != nil {
jsonError(c, common.CodeServerError, err.Error())
return