How Plane Handles Workspace-Level Permission Checks and Authorization in Django
The Plane Django backend enforces workspace-level security through Django REST Framework (DRF) custom permission classes that query the WorkspaceMember model to verify user roles and activity status before allowing access to protected endpoints.
Plane (makeplane/plane) implements a robust role-based access control (RBAC) system for workspace resources using Django REST Framework's permission framework. The backend relies on a centralized set of custom permission classes defined in apps/api/plane/utils/permissions/workspace.py to validate every request against a single source of truth: the WorkspaceMember model. This architecture ensures consistent authorization across all workspace-related API endpoints, from state management to member administration.
Role Hierarchy and Membership Model
Plane assigns workspace roles using integer constants that define a clear hierarchy. According to the source code in apps/api/plane/utils/permissions/workspace.py, the system recognizes three distinct permission levels:
- Admin = 20
- Member = 15
- Guest = 5
These values are stored on the WorkspaceMember model (plane.db.models.WorkspaceMember) and persist in the database alongside a membership status flag. The permission system specifically checks the is_active=True field to ensure that only active members can access workspace resources, preventing unauthorized access from suspended or removed users.
Core Permission Classes
The permission architecture centers on WorkSpaceBasePermission, which implements generic authorization rules that specialized classes extend.
WorkSpaceBasePermission
This base class defines the fundamental access control logic:
- Anonymous users are immediately denied access.
- POST requests (workspace creation) are permitted for any authenticated user.
- Safe methods (
GET,HEAD,OPTIONS) are allowed for active workspace members. - PUT/PATCH requests require the user to hold an Admin or Member role.
- DELETE requests require the user to be an Admin (workspace owner).
Specialized Permission Classes
Building on the base logic, Plane provides several specialized classes for specific access patterns:
WorkspaceOwnerPermission: Restricts access to users with the Admin role (20) only.WorkspaceAdminPermission: Grants access to users with Admin or Member roles (20 or 15).WorkspaceEntityPermission: Used for most entity-level endpoints; allows safe-method reads for any active member while restricting writes to admins or members.WorkspaceViewerPermissionandWorkspaceUserPermission: Provide read-only access for any active workspace member regardless of specific role tier.
Permission Flow in DRF Views
When a request reaches a workspace endpoint, Django REST Framework evaluates the view's permission_classes attribute before executing any view logic.
Request Evaluation Process
The permission check follows this deterministic flow:
- DRF calls
has_permission(request, view)on the configured permission class. - The class extracts
request.userandview.workspace_slugfrom the request context. - It queries the
WorkspaceMembertable to verify an active membership link between the user and the workspace identified byworkspace_slug. - It validates the user's role integer against the requirements for the specific HTTP method.
- If validation fails, the system returns a 403 Forbidden response immediately, preventing unauthorized code execution.
View Integration Examples
Concrete views in apps/api/plane/app/views/workspace/ attach these permissions via the permission_classes list. For example, the state management view uses WorkspaceEntityPermission to protect CRUD operations:
# apps/api/plane/app/views/workspace/state.py
from plane.app.permissions import WorkspaceEntityPermission
class WorkspaceStateView(APIView):
permission_classes = [WorkspaceEntityPermission] # Protects all HTTP methods
def get(self, request, workspace_slug):
# Safe method; any active member can read
...
def post(self, request, workspace_slug):
# Write operation; only Admin/Member can create
...
def delete(self, request, workspace_slug):
# Delete operation; only Admin can delete
...
For finer-grained control, views can combine class-level permissions with method decorators. The member management view demonstrates this pattern using the @allow_permission decorator:
# apps/api/plane/app/views/workspace/member.py
from plane.app.permissions import WorkspaceEntityPermission, allow_permission, ROLE
class WorkspaceMemberView(APIView):
permission_classes = [WorkspaceEntityPermission]
def get(self, request, workspace_slug):
# List members – any active member can view
...
@allow_permission(ROLE.ADMIN) # Custom decorator enforcing ADMIN role
def put(self, request, workspace_slug):
# Update member roles – only admins can modify
...
Key Implementation Files
The authorization system spans several critical files within the repository:
apps/api/plane/utils/permissions/workspace.py: Defines all workspace permission classes and role constants.apps/api/plane/app/permissions/workspace.py: Maintains a duplicate copy of permission definitions for backward compatibility.apps/api/plane/app/views/workspace/: Directory containing concrete view implementations (e.g.,state.py,member.py) that attach permission classes.plane/db/models.py: Contains theWorkspaceMembermodel serving as the authorization source of truth.
Summary
- DRF custom permission classes centralize authorization logic in
apps/api/plane/utils/permissions/workspace.py, ensuring consistent security enforcement. - Role hierarchy uses integer constants (20 for Admin, 15 for Member, 5 for Guest) stored in the
WorkspaceMembermodel to determine access levels. - Active membership verification requires
is_active=Trueon theWorkspaceMemberrecord, preventing access by suspended users. WorkSpaceBasePermissionprovides HTTP method-based access control, allowing safe reads for all active members while restricting writes to higher roles.- Specialized classes like
WorkspaceEntityPermissionandWorkspaceOwnerPermissionoffer granular control for specific endpoint patterns. - Early rejection occurs when
has_permissionfails, returning 403 responses before view logic executes, minimizing security exposure.
Frequently Asked Questions
What file contains the workspace permission classes in Plane?
The primary definitions reside in apps/api/plane/utils/permissions/workspace.py. A duplicate copy exists at apps/api/plane/app/permissions/workspace.py for backward compatibility, but both define the same role constants and permission classes like WorkspaceEntityPermission and WorkSpaceBasePermission.
How does Plane verify workspace membership during authorization?
The permission classes query the WorkspaceMember model using request.user and view.workspace_slug to locate an active membership record. The system specifically checks that is_active=True and validates the role integer against the required threshold for the requested HTTP method, ensuring only current, properly-roled users gain access.
Can guest users modify workspace resources?
No. Guests (role = 5) are restricted to read-only operations. According to the WorkSpaceBasePermission logic, write operations such as PUT, PATCH, and POST require at least Member level (15) or Admin level (20) depending on the specific permission class applied. Only workspace owners (Admins) can execute DELETE operations on workspace-level resources.
How are specific HTTP methods protected differently in the permission system?
The WorkSpaceBasePermission class implements method-based discrimination: safe methods (GET, HEAD, OPTIONS) are permitted for any active member, while PUT and PATCH require Admin or Member roles. DELETE operations are restricted to Admins only. Views can further specialize this behavior by using classes like WorkspaceViewerPermission for read-only endpoints or applying the @allow_permission(ROLE.ADMIN) decorator to specific methods requiring elevated privileges.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →