Files
igny8/backend/igny8_core/api/permissions.py
IGNY8 VPS (Salman) 7a35981038 feat(multi-tenancy): implement critical fixes for orphaned users and permissions
- Simplified HasTenantAccess permission logic to ensure every authenticated user has an account.
- Added fallback to system account for OpenAI settings in AI configuration.
- Allowed any authenticated user to check task progress in IntegrationSettingsViewSet.
- Created a script to identify and fix orphaned users without accounts.
- Updated error response handling in business endpoints for clarity.
2025-12-10 09:51:06 +00:00

208 lines
7.9 KiB
Python

"""
Standardized Permission Classes
Provides consistent permission checking across all endpoints
"""
from rest_framework import permissions
from rest_framework.exceptions import PermissionDenied
class IsAuthenticatedAndActive(permissions.BasePermission):
"""
Permission class that requires user to be authenticated and active
Base permission for most endpoints
"""
def has_permission(self, request, view):
import logging
logger = logging.getLogger(__name__)
if not request.user or not request.user.is_authenticated:
logger.warning(f"[IsAuthenticatedAndActive] DENIED: User not authenticated")
return False
# Check if user is active
if hasattr(request.user, 'is_active'):
is_active = request.user.is_active
if is_active:
logger.info(f"[IsAuthenticatedAndActive] ALLOWED: User {request.user.email} is active")
else:
logger.warning(f"[IsAuthenticatedAndActive] DENIED: User {request.user.email} is inactive")
return is_active
logger.info(f"[IsAuthenticatedAndActive] ALLOWED: User {request.user.email} (no is_active check)")
return True
class HasTenantAccess(permissions.BasePermission):
"""
Permission class that requires user to belong to the tenant/account
Ensures tenant isolation
Superusers, developers, and system account users bypass this check.
CRITICAL: Every authenticated user MUST have an account.
The middleware sets request.account from request.user.account.
If a user doesn't have an account, it's a data integrity issue.
"""
def has_permission(self, request, view):
import logging
logger = logging.getLogger(__name__)
if not request.user or not request.user.is_authenticated:
logger.warning(f"[HasTenantAccess] DENIED: User not authenticated")
return False
# Bypass for superusers
if getattr(request.user, 'is_superuser', False):
logger.info(f"[HasTenantAccess] ALLOWED: User {request.user.email} is superuser")
return True
# Bypass for developers
if hasattr(request.user, 'role') and request.user.role == 'developer':
logger.info(f"[HasTenantAccess] ALLOWED: User {request.user.email} is developer")
return True
# Bypass for system account users
try:
if hasattr(request.user, 'is_system_account_user') and request.user.is_system_account_user():
logger.info(f"[HasTenantAccess] ALLOWED: User {request.user.email} is system account user")
return True
except Exception:
pass
# SIMPLIFIED LOGIC: Every authenticated user MUST have an account
# Middleware already set request.account from request.user.account
# Just verify it exists
if not hasattr(request.user, 'account'):
logger.warning(f"[HasTenantAccess] DENIED: User {request.user.email} has no account attribute")
return False
try:
# Access the account to trigger any lazy loading
user_account = request.user.account
if not user_account:
logger.warning(f"[HasTenantAccess] DENIED: User {request.user.email} has NULL account")
return False
# Success - user has a valid account
logger.info(f"[HasTenantAccess] ALLOWED: User {request.user.email} has account {user_account.name} (ID: {user_account.id})")
return True
except (AttributeError, Exception) as e:
# User doesn't have account relationship - data integrity issue
logger.warning(f"[HasTenantAccess] DENIED: User {request.user.email} account access failed: {e}")
return False
class IsViewerOrAbove(permissions.BasePermission):
"""
Permission class that requires viewer, editor, admin, or owner role
For read-only operations
Superusers and developers bypass this check.
"""
def has_permission(self, request, view):
import logging
logger = logging.getLogger(__name__)
if not request.user or not request.user.is_authenticated:
logger.warning(f"[IsViewerOrAbove] DENIED: User not authenticated")
return False
# Bypass for superusers
if getattr(request.user, 'is_superuser', False):
logger.info(f"[IsViewerOrAbove] ALLOWED: User {request.user.email} is superuser")
return True
# Bypass for developers
if hasattr(request.user, 'role') and request.user.role == 'developer':
logger.info(f"[IsViewerOrAbove] ALLOWED: User {request.user.email} is developer")
return True
# Check user role
if hasattr(request.user, 'role'):
role = request.user.role
# viewer, editor, admin, owner all have access
allowed = role in ['viewer', 'editor', 'admin', 'owner']
if allowed:
logger.info(f"[IsViewerOrAbove] ALLOWED: User {request.user.email} has role {role}")
else:
logger.warning(f"[IsViewerOrAbove] DENIED: User {request.user.email} has invalid role {role}")
return allowed
# If no role system, allow authenticated users
logger.info(f"[IsViewerOrAbove] ALLOWED: User {request.user.email} (no role system)")
return True
class IsEditorOrAbove(permissions.BasePermission):
"""
Permission class that requires editor, admin, or owner role
For content operations
Superusers and developers bypass this check.
"""
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
# Bypass for superusers
if getattr(request.user, 'is_superuser', False):
return True
# Bypass for developers
if hasattr(request.user, 'role') and request.user.role == 'developer':
return True
# Check user role
if hasattr(request.user, 'role'):
role = request.user.role
# editor, admin, owner have access
return role in ['editor', 'admin', 'owner']
# If no role system, allow authenticated users
return True
class IsAdminOrOwner(permissions.BasePermission):
"""
Permission class that requires admin or owner role only
For settings, keys, billing operations
Superusers and developers bypass this check.
"""
def has_permission(self, request, view):
if not request.user or not request.user.is_authenticated:
return False
# Bypass for superusers
if getattr(request.user, 'is_superuser', False):
return True
# Bypass for developers
if hasattr(request.user, 'role') and request.user.role == 'developer':
return True
# Check user role
if hasattr(request.user, 'role'):
role = request.user.role
# admin, owner have access
return role in ['admin', 'owner']
# If no role system, deny by default for security
return False
class IsSystemAccountOrDeveloper(permissions.BasePermission):
"""
Allow only system accounts (aws-admin/default-account/default) or developer role.
Use for sensitive, globally-scoped settings like integration API keys.
"""
def has_permission(self, request, view):
user = getattr(request, "user", None)
if not user or not user.is_authenticated:
return False
account_slug = getattr(getattr(user, "account", None), "slug", None)
if user.role == "developer":
return True
if account_slug in ["aws-admin", "default-account", "default"]:
return True
return False