Dataset Permission Model#
Dify's dataset access control uses two coexisting, independently-evaluated layers that must remain synchronized :
- Legacy resource-level enum β stored on every
Datasetrow; evaluated unconditionally on all editions. - Enterprise RBAC whitelist β gated by the
RBAC_ENABLEDconfig flag; active only in enterprise deployments.
When RBAC is enabled, both layers run independently. Mismatches cause authorization errors for users who appear correctly permissioned in one layer but not the other.
Legacy Permission Enum#
PermissionEnum (in api/models/enums.py) defines three dataset visibility levels:
| Value | Meaning |
|---|---|
only_me | Only the dataset maintainer can access |
all_team_members | All workspace members can access |
partial_members | Only explicitly listed members (via the DatasetPermission join table) can access |
DatasetPermissionEnum in api/models/dataset.py is a backward-compatible alias for PermissionEnum; new code should import PermissionEnum from models.enums directly.
Legacy Enforcement Functions#
DatasetService.check_dataset_permission() is the primary read-path gate (called from datasets_document.py and other controllers):
- Rejects cross-tenant access unconditionally.
- Bypasses all further checks for the workspace
OWNERrole. - Enforces
ONLY_MEby comparingdataset.maintainerto the requesting user. - Enforces
PARTIAL_TEAMby querying theDatasetPermissionjoin table for an explicit record.
check_dataset_operator_permission() applies the same logic for write/mutation paths.
Critical gap: Neither function contains an RBAC_ENABLED branch . When enterprise RBAC is active, these functions still run unconditionally and may block access that RBAC would permit β or allow access RBAC should block . PR #41022 added an if not dify_config.RBAC_ENABLED guard to the dataset GET endpoint in datasets.py. As of PR #42392, multiple document and dataset API routes have proper RBAC resource-level permission checks that run alongside the legacy checks.
Enterprise RBAC Layer#
RBACPermission in api/core/rbac/entities.py defines dataset-scoped permission scenes: DATASET_PREVIEW, DATASET_READONLY, DATASET_USE, DATASET_EDIT, DATASET_CREATE_AND_MANAGEMENT, DATASET_DELETE, DATASET_ACCESS_CONFIG, and others. DATASET_CREATE_AND_MANAGEMENT is workspace-scoped (no specific resource ID required).
enforce_rbac_checks() in api/controllers/common/rbac/checks.py is the RBAC enforcement entry point:
- Short-circuits immediately if
RBAC_ENABLEDisFalse(line 39β40). - Grants access if the requesting account matches the resource owner's
owner_id(lines 53β55). - Otherwise calls
RBACService.CheckAccess.check()to evaluate the permission scene.
RBACResourceScope distinguishes workspace-level checks (WORKSPACE) from resource-scoped checks (APP, DATASET, AGENT).
The RBAC_ENABLED flag lives at api/configs/enterprise/__init__.py. Both the backend flag and the frontend NEXT_PUBLIC_RBAC_ENABLED flag must be aligned β mismatching them causes 404s or missing RBAC enforcement .
When RBAC is disabled, _LEGACY_MY_PERMISSIONS in rbac_service.py maps legacy tenant roles (OWNER/ADMIN/EDITOR, DATASET_OPERATOR, NORMAL) to dataset.acl.* permission keys, providing the same shape the RBAC API returns .
RBAC Route Coverage#
As of PR #42392, the following dataset/document API routes have proper RBAC resource-level permission checks:
DATASET_READONLY permission:
- Document list:
GET /datasets/<uuid:dataset_id>/documents - Document detail:
GET /datasets/<uuid:dataset_id>/documents/<uuid:document_id> - Document indexing status:
GET /datasets/<uuid:dataset_id>/documents/<uuid:document_id>/indexing-status - Batch indexing status:
GET /datasets/<uuid:dataset_id>/documents/<batch>/indexing-status - Document pipeline execution log:
GET /datasets/<uuid:dataset_id>/documents/<uuid:document_id>/pipeline-execution-log - Document summary status:
GET /datasets/<uuid:dataset_id>/documents/<uuid:document_id>/summary-status - Process rule retrieval (via document query param):
GET /datasets/process-rule?document_id=...
DATASET_USE permission:
- Dataset indexing estimate (existing dataset):
POST /datasets/<uuid:dataset_id>/indexing-estimate(withdataset_idpresent) - Document indexing estimate:
GET /datasets/<uuid:dataset_id>/documents/<uuid:document_id>/indexing-estimate - Batch indexing estimate:
GET /datasets/<uuid:dataset_id>/documents/<batch>/indexing-estimate
DATASET_EDIT permission:
- Dataset Notion sync:
GET /datasets/<uuid:dataset_id>/documents/notion-sync-... - Document Notion sync:
GET /datasets/<uuid:dataset_id>/documents/<uuid:document_id>/notion-sync-...
Workspace-level DATASET_CREATE_AND_MANAGEMENT permission:
- Dataset indexing estimate (new dataset):
POST /datasets/indexing-estimate(whendataset_idisnull)
These routes previously required DATASET_CREATE_AND_MANAGEMENT at the workspace level when RBAC was enabled, causing 403 Forbidden errors for members with only resource-level access.
Sync Requirements: Legacy β RBAC#
Whenever Dataset.permission changes, the RBAC access whitelist must also be updated. The invariant :
| Legacy enum value | RBAC whitelist behavior |
|---|---|
all_team_members | automatic_include_workspace_members=False; members must be explicitly granted access |
partial_members or only_me | automatic_include_workspace_members=False |
As of PR #42430, all dataset creation paths use automatic_include_workspace_members=False. Workspace members must now be explicitly granted access β they are no longer automatically included at creation time .
Concrete failure mode: A dataset created via file upload defaults to permission=only_me and gets no RBAC whitelist. Even after the maintainer sets "Partial team members" in the RBAC UI, the legacy gate runs first and rejects with You do not have permission to access this dataset. .
RBAC Initialization Coverage by Creation Path#
| Entry point | RBAC initialized? | Notes |
|---|---|---|
| Console empty dataset | β Partial | DatasetAccess.replace_whitelist with automatic_include_workspace_members=False |
Public API (service_api) create | β Partial | Same as above |
File upload (save_document_without_dataset_id) | β Partial | Fixed in PR #40599 |
| External knowledge base | β Yes | Fixed in PR #39930 |
| RAG pipeline DSL import | β Yes | Fixed in PR #42430 |
| RAG pipeline empty dataset | β No | dataset_service.py:532 |
Known Open Issues#
| Issue | Status |
|---|---|
| #39379 β RBAC not initialized on most creation paths | Partially open: RAG pipeline empty dataset path still missing |
| #39037 β Code paths invoke only legacy check, never updating RBAC | Fixed: PR #42392 added RBAC checks to document/dataset routes |
#38910 β all_team_members β workspace member sync missing | Fixed: PR #39735 added initialize_created_app_rbac_access_task dispatch |
#39226 β partial_members/only_me β RBAC sync missing | Open: RBAC whitelist config must be updated to automatic_include_workspace_members=False on legacy permission change |
Key Files#
| File | Purpose |
|---|---|
api/models/enums.py | PermissionEnum definition |
api/models/dataset.py | DatasetPermissionEnum alias, Dataset model, DatasetPermission join table |
api/services/dataset_service.py | check_dataset_permission, check_dataset_operator_permission |
api/core/rbac/entities.py | RBACPermission scenes, RBACResourceScope |
api/controllers/common/rbac/checks.py | enforce_rbac_checks() β RBAC enforcement entry point |
api/services/enterprise/rbac_service.py | _LEGACY_MY_PERMISSIONS, DatasetPermissions, MyPermissions, CheckAccess |
api/controllers/console/datasets/datasets_document.py | Document and batch API endpoints with RBAC enforcement |
api/tasks/initialize_created_app_rbac_access_task.py | Celery tasks for RBAC initialization and workspace-member sync |