User Roles and Permissions#
Overview#
Dify uses a dual-layer permission model: a legacy workspace role system (always active) and an optional enterprise RBAC system (gated by RBAC_ENABLED). The two layers must be aligned across the backend config and frontend env vars, or access denials and management UI errors will occur.
Role Model#
Legacy Roles (TenantAccountRole)#
Five roles defined in api/models/account.py:
| Role | Value |
|---|---|
| Owner | owner |
| Admin | admin |
| Editor | editor |
| Normal | normal |
| Dataset Operator | dataset_operator |
Roles are stored in the TenantAccountJoin table which links accounts to tenants. The TenantService methods get_user_role, has_roles, and check_member_permission are the main read paths.
Ownership#
- One workspace has exactly one
ownerat a time. - Ownership transfer is console-only (via
OwnerTransferendpoint); the OpenAPIPATCH /workspaces/{id}/members/{mid}endpoint explicitly rejectsownerrole assignments. - Transfer requires an email verification code sent from
SendOwnerTransferEmailApiand confirmed viaOwnerTransferCheckApibefore the finalOwnerTransfer.post()call. - When RBAC is enabled, ownership also updates the RBAC binding via
RBACService.MemberRoles.replace.
RBAC System (Enterprise)#
When RBAC_ENABLED=true, every legacy role maps to a builtin RBAC role with a matching role_tag. The bridge is AccountService._resolve_legacy_role_id() which looks up the builtin RBAC role for a given TenantAccountRole.
Configuration Flags (Must Be Aligned)#
| Layer | Flag | Location | Default |
|---|---|---|---|
| Backend | RBAC_ENABLED | api/configs/enterprise/__init__.py:32-35 | false |
| Frontend | NEXT_PUBLIC_RBAC_ENABLED | web/env.ts:96 | false |
If these are out of sync, the frontend will call RBAC endpoints (e.g. /workspaces/current/rbac/...) that the backend doesn't serve (returning 404), or vice versa. This was the root cause of the v3.11 management backend error.
Permission Checks (RBAC Mode)#
When RBAC is enabled, check_member_permission reads workspace.member.manage and workspace.role.manage permission keys from RBACService.MyPermissions rather than checking legacy role hierarchy. The member list endpoint fetches RBAC role data via RBACService.MemberRoles.batch_get and serializes it alongside the legacy role.
Member Invitation & Lifecycle#
Key service: RegisterService.invite_new_member().
Flow:
- If no existing account β create with
AccountStatus.PENDING, createTenantAccountJoin, send invite email. - If existing account:
- If no existing
TenantAccountJoinAND (account is PENDING OR RBAC is enabled) β create member join. - If account is ACTIVE and RBAC is disabled β
AccountAlreadyInTenantError(the owner invite bug β see Known Bugs).
- If no existing
When RBAC is enabled, invite_new_member also calls RBACService.MemberRoles.replace with the invited role.
Invited members are re-assigned NORMAL role at the TenantAccountJoin level when RBAC is enabled (the RBAC role carries the actual permission).
The critical condition at line 2095:
if not ta and (account.status == AccountStatus.PENDING or dify_config.RBAC_ENABLED):
TenantService.create_tenant_member(tenant, account, session, tenant_join_role)
When inviting an active account (e.g. a workspace owner from another tenant) with RBAC_ENABLED=false, this evaluates to false and create_tenant_member() is never called.
Known Bugs#
[v1.15.0] Inviting an existing workspace owner silently fails (Issue #38048)#
Root cause: The invite_new_member condition at line 2095 only creates a TenantAccountJoin for ACTIVE accounts when RBAC_ENABLED=true. With RBAC off, inviting an active account (such as an owner of another workspace) sends the email but never adds them to the workspace. Fix: PR #37479 + follow-up PR #38101.
[v1.15.0] Re-inviting a removed member shows "workspace not found" (Issue #38073)#
Root cause (Bug 1): After removal, the re-invited account has no current workspace, causing the login check to fail before activation. Fix: PR #38087.
Root cause (Bug 2): The /activate endpoint lacked session validation β if an admin opened the invite link in the same browser, the admin account consumed the token. Fix: PR #39438 (July 2026). The endpoint now validates that the logged-in session (if present) belongs to the account encoded in the invitation token before consuming the token or changing tenant membership. Mismatches return a 403 InvitationAccountMismatchError.
[v3.11] 404 on RBAC management endpoints (Issue #38272)#
Root cause: Frontend/backend RBAC_ENABLED flag mismatch. The enterprise RBAC endpoints (/workspaces/current/rbac/...) are only registered when RBAC_ENABLED=true on the backend. Setting NEXT_PUBLIC_RBAC_ENABLED=false in the frontend and clearing the browser cache resolves the symptom for non-enterprise deployments. Enterprise users should contact Zendesk support.
Key Files#
| File | Purpose |
|---|---|
api/models/account.py | TenantAccountRole enum, TenantAccountJoin model |
api/services/account_service.py | TenantService, RegisterService.invite_new_member, RBAC integration |
api/controllers/console/workspace/members.py | Member invite, role update, ownership transfer endpoints |
api/controllers/openapi/workspaces.py | OpenAPI workspace/member endpoints (bearer auth) |
api/configs/enterprise/__init__.py | RBAC_ENABLED backend flag |
web/env.ts | NEXT_PUBLIC_RBAC_ENABLED frontend flag |