## Problem
`POST /api/v1/folders/{folder_id}/commits` (also reachable as
`/workspaces/{workspace_id}/commits` and
`/datasets/{dataset_id}/commits`)
authorizes only the folder in the URL. `FileCommitService.CreateCommit`
then
applies every item of the client-supplied `files` array straight to the
`file`
table:
```go
tx.Model(&entity.File{}).Where("id = ?", change.FileID).Updates(map[string]interface{}{
"location": objKey, "size": fSize,
})
```
None of the `add` / `modify` / `delete` / `rename` branches constrain
the
write by tenant or by folder, so an authenticated caller who is allowed
to
commit in *any* workspace can pass the ID of a file owned by another
workspace
or another tenant and rewrite its `location`, `size`, `name`, or
`status`.
That is the IDOR/CWE-639 class the DAO already documents on
`FileDAO.GetByIDAndTenant`, and the sibling write paths (`DeleteFiles`,
`MoveFiles`) authorize every file they touch.
### What problem does this PR solve?
Remove service-layer permission wrappers that only forwarded calls, and
invoke the permission layer directly at call sites. Keep composite
authorization rules with their owning business flows.
### Type of change
- [x] Refactor (no functional change)
Fixes#19356
The file-commit endpoints resolved a dataset or folder id but never
checked that the caller can access it. Any logged-in user could read
another tenant's commit history, diffs, and file contents, pull version
history for any file id, and create commits in a workspace they don't
own. Sibling artifact routes in dataset_api.py apply
KnowledgebaseService.accessible for the same data; these did not.
### What problem does this PR solve?
| # | Method | Endpoint | Description | Git Equivalent |
|---|--------|----------|-------------|----------------|
| 1 | `POST` | `/api/v1/{prefix}/{folder_id}/commits` | Create a
snapshot commit with file changes (add/modify/delete/rename) | `git add`
+ `git commit` |
| 2 | `GET` | `/api/v1/{prefix}/{folder_id}/commits` | List commit
history (paginated) | `git log` |
| 3 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}` | Get
commit detail with file changes | `git show` |
| 4 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files` |
List file changes in a commit | `git show --name-status` |
| 5 | `GET` |
`/api/v1/{prefix}/{folder_id}/commits/diff?from=...&to=...` | Compare
two commits and return differences | `git diff` |
| 6 | `GET` | `/api/v1/{prefix}/{folder_id}/changes` | Get uncommitted
changes (add/modify/delete) | `git status` |
| 7 | `GET` | `/api/v1/{prefix}/{folder_id}/commits/{commit_id}/tree` |
Get the folder tree snapshot at commit time | `git ls-tree` |
| 8 | `GET` |
`/api/v1/{prefix}/{folder_id}/commits/{commit_id}/files/{file_id}/content`
| Get a file's content as it existed in a specific commit | `git show
HEAD:file` |
| 9 | `GET` | `/api/v1/{prefix}/{file_id}/versions` | Get version
history for a specific file across all commits | `git log -- file` |
Where `{prefix}/{id}` can be:
- `folders/{folder_id}` — direct folder access
- `workspaces/{workspace_id}` — alias of `folders/{folder_id}`
- `datasets/{dataset_id}` — resolves to the dataset's folder
- `memories/{memory_id}` — resolves to the memory's folder
- `skills/{skill_id}` — resolves to the skill's folder
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
- [x] Documentation Update