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.
This commit is contained in:
@@ -12,13 +12,23 @@ class IsAuthenticatedAndActive(permissions.BasePermission):
|
||||
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'):
|
||||
return 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
|
||||
|
||||
|
||||
@@ -27,47 +37,58 @@ 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
|
||||
|
||||
# Get account from request (set by middleware)
|
||||
account = getattr(request, 'account', None)
|
||||
# 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
|
||||
|
||||
# If no account in request, try to get from user
|
||||
if not account and hasattr(request.user, 'account'):
|
||||
try:
|
||||
account = request.user.account
|
||||
except (AttributeError, Exception):
|
||||
pass
|
||||
|
||||
# Regular users must have account access
|
||||
if account:
|
||||
# Check if user belongs to this account
|
||||
if hasattr(request.user, 'account'):
|
||||
try:
|
||||
user_account = request.user.account
|
||||
return user_account == account or user_account.id == account.id
|
||||
except (AttributeError, Exception):
|
||||
pass
|
||||
|
||||
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):
|
||||
@@ -77,24 +98,36 @@ class IsViewerOrAbove(permissions.BasePermission):
|
||||
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
|
||||
return role in ['viewer', 'editor', 'admin', 'owner']
|
||||
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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user