""" Moderation Permissions This module contains custom permission classes for the moderation system, providing role-based access control for moderation operations. Each permission class includes an `as_guard()` class method that converts the permission to an FSM guard function, enabling alignment between API permissions and FSM transition checks. """ from typing import Callable, Any, Optional from rest_framework import permissions from django.contrib.auth import get_user_model User = get_user_model() class PermissionGuardAdapter: """ Adapter that wraps a DRF permission class as an FSM guard. This allows DRF permission classes to be used as conditions for FSM transitions, ensuring consistent authorization between API endpoints and state transitions. Example: guard = IsModeratorOrAdmin.as_guard() # Use in FSM transition conditions @transition(conditions=[guard]) def approve(self, user=None): pass """ def __init__( self, permission_class: type, error_message: Optional[str] = None, ): """ Initialize the guard adapter. Args: permission_class: The DRF permission class to adapt error_message: Custom error message on failure """ self.permission_class = permission_class self._custom_error_message = error_message self._last_error_code: Optional[str] = None @property def error_code(self) -> Optional[str]: """Return the error code from the last failed check.""" return self._last_error_code def __call__(self, instance: Any, user: Any = None) -> bool: """ Check if the permission passes for the given user. Args: instance: Model instance being transitioned user: User attempting the transition Returns: True if the permission check passes """ self._last_error_code = None if user is None: self._last_error_code = "NO_USER" return False # Create a mock request object for DRF permission check class MockRequest: def __init__(self, user): self.user = user self.data = {} self.method = "POST" mock_request = MockRequest(user) permission = self.permission_class() # Check permission if not permission.has_permission(mock_request, None): self._last_error_code = "PERMISSION_DENIED" return False # Check object permission if available if hasattr(permission, "has_object_permission"): if not permission.has_object_permission(mock_request, None, instance): self._last_error_code = "OBJECT_PERMISSION_DENIED" return False return True def get_error_message(self) -> str: """Return user-friendly error message.""" if self._custom_error_message: return self._custom_error_message return f"Permission denied by {self.permission_class.__name__}" def get_required_roles(self) -> list: """Return list of roles that would satisfy this permission.""" # Try to infer from permission class name name = self.permission_class.__name__ if "Superuser" in name: return ["SUPERUSER"] elif "Admin" in name: return ["ADMIN", "SUPERUSER"] elif "Moderator" in name: return ["MODERATOR", "ADMIN", "SUPERUSER"] return ["USER", "MODERATOR", "ADMIN", "SUPERUSER"] class GuardMixin: """ Mixin that adds guard adapter functionality to DRF permission classes. """ @classmethod def as_guard(cls, error_message: Optional[str] = None) -> Callable: """ Convert this permission class to an FSM guard function. Args: error_message: Optional custom error message Returns: Guard function compatible with FSM transition conditions Example: guard = IsModeratorOrAdmin.as_guard() # In transition definition @transition(conditions=[guard]) def approve(self, user=None): pass """ return PermissionGuardAdapter(cls, error_message=error_message) class IsModerator(GuardMixin, permissions.BasePermission): """ Permission that only allows moderators to access the view. Use `IsModerator.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated and has moderator role.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") return user_role == "MODERATOR" def has_object_permission(self, request, view, obj): """Check object-level permissions for moderators.""" return self.has_permission(request, view) class IsModeratorOrAdmin(GuardMixin, permissions.BasePermission): """ Permission that allows moderators, admins, and superusers to access the view. Use `IsModeratorOrAdmin.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated and has moderator, admin, or superuser role.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") return user_role in ["MODERATOR", "ADMIN", "SUPERUSER"] def has_object_permission(self, request, view, obj): """Check object-level permissions for moderators and admins.""" return self.has_permission(request, view) class IsAdminOrSuperuser(GuardMixin, permissions.BasePermission): """ Permission that only allows admins and superusers to access the view. Use `IsAdminOrSuperuser.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated and has admin or superuser role.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") return user_role in ["ADMIN", "SUPERUSER"] def has_object_permission(self, request, view, obj): """Check object-level permissions for admins and superusers.""" return self.has_permission(request, view) class CanViewModerationData(GuardMixin, permissions.BasePermission): """ Permission that allows users to view moderation data based on their role. - Regular users can only view their own reports - Moderators and above can view all moderation data Use `CanViewModerationData.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated.""" return request.user and request.user.is_authenticated def has_object_permission(self, request, view, obj): """Check object-level permissions for viewing moderation data.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") # Moderators and above can view all data if user_role in ["MODERATOR", "ADMIN", "SUPERUSER"]: return True # Regular users can only view their own reports if hasattr(obj, "reported_by"): return obj.reported_by == request.user # For other objects, deny access to regular users return False class CanModerateContent(GuardMixin, permissions.BasePermission): """ Permission that allows users to moderate content based on their role. - Only moderators and above can moderate content - Includes additional checks for specific moderation actions Use `CanModerateContent.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated and has moderation privileges.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") return user_role in ["MODERATOR", "ADMIN", "SUPERUSER"] def has_object_permission(self, request, view, obj): """Check object-level permissions for content moderation.""" if not self.has_permission(request, view): return False user_role = getattr(request.user, "role", "USER") # Superusers can do everything if user_role == "SUPERUSER": return True # Admins can moderate most content but may have some restrictions if user_role == "ADMIN": # Add any admin-specific restrictions here if needed return True # Moderators have basic moderation permissions if user_role == "MODERATOR": # Add any moderator-specific restrictions here if needed # For example, moderators might not be able to moderate admin actions if hasattr(obj, "moderator") and obj.moderator: moderator_role = getattr(obj.moderator, "role", "USER") if moderator_role in ["ADMIN", "SUPERUSER"]: return False return True return False class CanAssignModerationTasks(GuardMixin, permissions.BasePermission): """ Permission that allows users to assign moderation tasks to others. - Moderators can assign tasks to themselves - Admins can assign tasks to moderators and themselves - Superusers can assign tasks to anyone Use `CanAssignModerationTasks.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated and has assignment privileges.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") return user_role in ["MODERATOR", "ADMIN", "SUPERUSER"] def has_object_permission(self, request, view, obj): """Check object-level permissions for task assignment.""" if not self.has_permission(request, view): return False user_role = getattr(request.user, "role", "USER") # Superusers can assign to anyone if user_role == "SUPERUSER": return True # Admins can assign to moderators and themselves if user_role == "ADMIN": return True # Moderators can only assign to themselves if user_role == "MODERATOR": # Check if they're trying to assign to themselves assignee_id = request.data.get("moderator_id") or request.data.get( "assigned_to" ) if assignee_id: return str(assignee_id) == str(request.user.id) return True return False class CanPerformBulkOperations(GuardMixin, permissions.BasePermission): """ Permission that allows users to perform bulk operations. - Only admins and superusers can perform bulk operations - Includes additional safety checks for destructive operations Use `CanPerformBulkOperations.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated and has bulk operation privileges.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") return user_role in ["ADMIN", "SUPERUSER"] def has_object_permission(self, request, view, obj): """Check object-level permissions for bulk operations.""" if not self.has_permission(request, view): return False user_role = getattr(request.user, "role", "USER") # Superusers can perform all bulk operations if user_role == "SUPERUSER": return True # Admins can perform most bulk operations if user_role == "ADMIN": # Add any admin-specific restrictions for bulk operations here # For example, admins might not be able to perform certain destructive operations operation_type = getattr(obj, "operation_type", None) if operation_type in ["DELETE_USERS", "PURGE_DATA"]: return False # Only superusers can perform these operations return True return False class IsOwnerOrModerator(GuardMixin, permissions.BasePermission): """ Permission that allows object owners or moderators to access the view. - Users can access their own objects - Moderators and above can access any object Use `IsOwnerOrModerator.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated.""" return request.user and request.user.is_authenticated def has_object_permission(self, request, view, obj): """Check object-level permissions for owners or moderators.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") # Moderators and above can access any object if user_role in ["MODERATOR", "ADMIN", "SUPERUSER"]: return True # Check if user is the owner of the object if hasattr(obj, "reported_by"): return obj.reported_by == request.user elif hasattr(obj, "created_by"): return obj.created_by == request.user elif hasattr(obj, "user"): return obj.user == request.user return False class CanManageUserRestrictions(GuardMixin, permissions.BasePermission): """ Permission that allows users to manage user restrictions and moderation actions. - Moderators can create basic restrictions (warnings, temporary suspensions) - Admins can create more severe restrictions (longer suspensions, content removal) - Superusers can create any restriction including permanent bans Use `CanManageUserRestrictions.as_guard()` to get an FSM-compatible guard. """ def has_permission(self, request, view): """Check if user is authenticated and has restriction management privileges.""" if not request.user or not request.user.is_authenticated: return False user_role = getattr(request.user, "role", "USER") return user_role in ["MODERATOR", "ADMIN", "SUPERUSER"] def has_object_permission(self, request, view, obj): """Check object-level permissions for managing user restrictions.""" if not self.has_permission(request, view): return False user_role = getattr(request.user, "role", "USER") # Superusers can manage any restriction if user_role == "SUPERUSER": return True # Get the action type from request data or object action_type = None if request.method in ["POST", "PUT", "PATCH"]: action_type = request.data.get("action_type") elif hasattr(obj, "action_type"): action_type = obj.action_type # Admins can manage most restrictions if user_role == "ADMIN": # Admins cannot create permanent bans if action_type == "USER_BAN" and request.data.get("duration_hours") is None: return False return True # Moderators can only manage basic restrictions if user_role == "MODERATOR": allowed_actions = ["WARNING", "CONTENT_REMOVAL", "USER_SUSPENSION"] if action_type not in allowed_actions: return False # Moderators can only create temporary suspensions (max 7 days) if action_type == "USER_SUSPENSION": duration_hours = request.data.get("duration_hours", 0) if duration_hours > 168: # 7 days = 168 hours return False return True return False