langgenius / langgenius/dify

RBAC: GET /datasets/batch_import_status/<job_id> always returns 400 when RBAC_ENABLED

Open
#39,954 2 comments 1 reaction 0 assignees View on GitHub
🐞 bug project#dify
Dominant language
TypeScript
Stars
156k
Forks
24.6k
Avg merge
22h 9m
Merged PRs (30d)
610

Description

### Self Checks

- [x] I have read the [Contributing Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) and [Language Policy](https://github.com/langgenius/dify/issues/1542).
- [x] This is only for bug report, if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general).
- [x] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
- [x] I confirm that I am using English to submit this report, otherwise it will be closed.
- [x] 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
- [x] Please do not modify this template :) and fill in all the required fields.

### Dify version

1.16.1 (`main`)

### Cloud or Self Hosted

Self Hosted (Docker), Self Hosted (Source)

### Steps to reproduce

Requires `RBAC_ENABLED=true`. With the default (`false`) the gate short-circuits and everything works.

1. Start a segment batch import:
`POST /console/api/datasets//documents//segments/batch_import`
→ `200 {"job_id": "...", "job_status": "waiting"}`
2. Poll that job for its status, as the UI does:
`GET /console/api/datasets/batch_import_status/`
3. → `400`

```json
{"code": "invalid_param", "message": "Missing dataset_id or pipeline_id in request path", "status": 400}
```

### ✔️ Expected Behavior

The status endpoint returns the batch import job status, gated on the caller's permission for the dataset the job belongs to.

### ❌ Actual Behavior

`400 invalid_param`. The view never runs, so segment batch import progress cannot be polled at all — the UI's polling call (`web/service/knowledge/use-segment.ts:213`) fails for every account, including workspace owners.

**Root cause**

`DatasetDocumentSegmentBatchImportApi` binds one `Resource` class to two routes (`api/controllers/console/datasets/datasets_segments.py:602-606`):

```python
@console_ns.route(
"/datasets//documents//segments/batch_import",
"/datasets/batch_import_status/",
)
class DatasetDocumentSegmentBatchImportApi(Resource):
```

Both methods carry dataset-scoped RBAC gates that keep the default `resource_required=True`:

- `post` → `RBACPermission.DATASET_EDIT` (`:617`)
- `get` → `RBACPermission.DATASET_READONLY` (`:671`)

Flask-RESTX binds every route on the class to every method, so both methods are reachable at `/datasets/batch_import_status/` — a path that carries no `dataset_id`. `enforce_rbac_access` then calls `_extract_resource_id` (`api/controllers/common/wraps.py:167-178`), which finds neither `dataset_id` nor `pipeline_id` and raises `ValueError`. That is mapped to `400` by `handle_value_error` (`api/libs/external_api.py:92-97`), before the view body executes.

**Suggested fix**

This is the same root cause as #39930, but the remedy is different. There, the permission key is workspace-level, so passing `resource_required=False` is correct. Here both keys are genuinely per-dataset, so suppressing the resource id would turn this into an unscoped permission check over a Redis key (`segment_batch_import_{job_id}`) that carries no tenant or dataset — a weaker guarantee than intended.

The route shape is what needs to change: give the status endpoint its own dataset-scoped path and its own `Resource` class, e.g.

```
/datasets//batch_import_status/
```

That also needs the frontend polling URL updated in `web/service/knowledge/use-segment.ts:213`.

Contributor guide

Open the contributing guide

Research direction

Start with DatasetDocumentSegmentBatchImportApi in api/controllers/console/datasets/datasets_segments.py and inspect enforce_rbac_access and _extract_resource_id in api/controllers/common/wraps.py. Check the polling call at web/service/knowledge/use-segment.ts:213 and verify the batch-import POST and status GET with RBAC_ENABLED=true. Done means the status route carries the dataset context, preserves dataset-scoped permission checks, and the UI polling request succeeds.

Written by the indexing model from the issue text.

Assessment

Tech stack
flask, python, typescript
Domain
api, authorization, backend, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.