Permissions System¶
Bifolk implements Role-Based Access Control (RBAC) where permissions are granted based on a user's role within an organization.
Target Audience: Software developers
Core Concepts¶
- Organizations: All business data belongs to an organization
- Membership: Users belong to organizations through
OrganizationMembership - Roles: Each membership has a role (Owner, Admin, Member, Viewer)
- Data Isolation: Users only see data from their organizations
Role Hierarchy¶
| Role | View | Create | Edit Own | Edit All | Delete | Manage Members | Delete Org |
|---|---|---|---|---|---|---|---|
| Owner | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Admin | Yes | Yes | Yes | Yes | Yes | Yes | No |
| Member | Yes | Yes | Yes | No | No | No | No |
| Viewer | Yes | No | No | No | No | No | No |
Permission Utility Functions¶
All centralized in organizations/utils.py.
Core Functions¶
| Function | Returns | Description |
|---|---|---|
get_user_role(user, organization) |
str or None |
User's role: 'owner', 'admin', 'member', 'viewer' |
get_user_organizations(user) |
QuerySet |
All organizations user belongs to |
user_is_member(user, organization) |
bool |
Active membership check |
Resource Permission Functions¶
| Function | Parameters | Rules |
|---|---|---|
user_can_view(user, resource) |
Resource with organization attr |
Any member can view |
user_can_create(user, organization) |
Organization | Member+ can create |
user_can_edit(user, resource) |
Resource with organization, created_by |
Owner/Admin: all; Member: own only |
user_can_delete(user, resource) |
Resource with organization |
Owner/Admin only |
Organization Management Functions¶
from organizations.utils import (
user_can_manage_members,
user_can_delete_org,
user_can_change_org_settings,
)
| Function | Rules |
|---|---|
user_can_manage_members(user, org) |
Owner/Admin |
user_can_delete_org(user, org) |
Owner only |
user_can_change_org_settings(user, org) |
Owner/Admin |
Query Filtering¶
from organizations.utils import filter_by_user_organizations
user_hives = filter_by_user_organizations(Hive.objects.all(), request.user)
Model-Level Permissions¶
All business models implement permission methods:
class Hive(models.Model):
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
def user_can_view(self, user):
# Any active member of the organization
...
def user_can_edit(self, user):
# Owner/Admin: all; Member: own only; Viewer: no
...
def user_can_delete(self, user):
# Owner/Admin only
...
Models with these methods: Hive, Queen, HiveInspection, HarvestRecord, all BeekeepingOperation subclasses, QueenBreeding, ColonySplit, InventoryItem, InventoryTransaction, ProductSale.
View-Level Permissions¶
In Views¶
from django.http import HttpResponseForbidden
from organizations.utils import user_can_edit
def edit_hive(request, hive_id):
hive = get_object_or_404(Hive, id=hive_id)
if not user_can_edit(request.user, hive):
return HttpResponseForbidden()
# proceed with edit...
Filtering Querysets¶
Always filter by current organization:
def hive_list(request):
if request.current_organization:
hives = Hive.objects.filter(organization=request.current_organization)
else:
hives = Hive.objects.none()
return render(request, 'hives/hive_list.html', {'hives': hives})
Template-Level Permissions¶
{% load organization_tags %}
{% if hive.user_can_edit user %}
<a href="{% url 'hives:edit' hive.id %}">Edit</a>
{% endif %}
{% if hive.user_can_delete user %}
<a href="{% url 'hives:delete' hive.id %}">Delete</a>
{% endif %}
Organization Middleware¶
OrganizationMiddleware (organizations/middleware.py) runs after AuthenticationMiddleware and provides:
request.current_organization- Active organization (from session or default)request.user_organizations- All organizations user belongs to
Switching: Users switch organizations via the sidebar selector. The selected organization is stored in the session.
Best Practices¶
- Always check permissions in views before modifying data
- Always filter querysets by
request.current_organization - Set organization on create:
obj.organization = request.current_organization - Use the utility functions instead of reimplementing permission logic
- Handle None organization for users without organizations
- Hide non-existent resources: Filter querysets to prevent revealing resource existence
# Prefer this (hides existence)
hive = get_object_or_404(Hive, id=hive_id, organization__in=request.user_organizations)
# Over this (reveals existence)
hive = get_object_or_404(Hive, id=hive_id)
if not hive.user_can_view(request.user):
return HttpResponseForbidden()
Related Documentation¶
- Database Models - Permission methods on models
- Architecture Overview - Multi-tenant design
- Organization Management - Admin perspective
- Internal AI Reference:
docs/claude/03-PERMISSIONS.mdfor complete function signatures