Fix Critical Security Issue: Authorization bypass on shared tasks

Added comprehensive authorization checks to prevent users from:
1. Sharing tasks/tags they don't own
2. Modifying tasks/tags that are only shared with them (read-only access)

Changes:

tasks/serializers.py (TaskShareSerializer):
- Added validation in validate() method to verify task/tag ownership
- Users can only share their own tasks and tags
- Prevents malicious users from sharing other people's resources

tasks/permissions.py (NEW):
- Created IsOwnerOrReadOnlyIfShared permission class
- Owners: full access (read, update, delete)
- Shared users: read-only access
- Prevents privilege escalation via shared access

tasks/views.py:
- Applied IsOwnerOrReadOnlyIfShared to TaskDetailAPIView
- Applied IsOwnerOrReadOnlyIfShared to TagDetailAPIView
- Enforces object-level permissions on all update/delete operations

Security impact:
- Prevents users from modifying or deleting resources they don't own
- Prevents users from sharing resources they don't own
- Maintains proper separation between owner and shared access levels

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Keith Smith
2025-12-22 22:38:43 -07:00
co-authored by Claude Sonnet 4.5
parent deabe6ab3d
commit c50aaa2d81
3 changed files with 41 additions and 2 deletions
+28
View File
@@ -0,0 +1,28 @@
"""
Custom permissions for task-related views.
"""
from rest_framework import permissions
class IsOwnerOrReadOnlyIfShared(permissions.BasePermission):
"""
Permission that allows:
- Owners: full access (read, update, delete)
- Shared users: read-only access
SECURITY: Prevents users with shared access from modifying tasks/tags they don't own.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed for shared tasks/tags
if request.method in permissions.SAFE_METHODS:
# Allow if user owns the object OR if it's shared with them
return (
obj.user == request.user or
hasattr(obj, 'shares') and obj.shares.filter(shared_with=request.user).exists() or
hasattr(obj, 'task') and obj.task.shares.filter(shared_with=request.user).exists()
)
# Write permissions (PUT, PATCH, DELETE) only for owners
return obj.user == request.user