Refactor API permissions and throttling: Updated default permission classes to enforce authentication and tenant access. Introduced new permission for system accounts and developers. Enhanced throttling rates for various operations to reduce false 429 errors. Improved API key loading logic to prioritize account-specific settings, with fallbacks to system accounts and Django settings. Updated integration views and sidebar to reflect new permission structure.
This commit is contained in:
@@ -43,32 +43,48 @@ class AICore:
|
||||
self._load_account_settings()
|
||||
|
||||
def _load_account_settings(self):
|
||||
"""Load API keys and model from IntegrationSettings or Django settings"""
|
||||
if self.account:
|
||||
"""Load API keys from IntegrationSettings with fallbacks (account -> system account -> Django settings)"""
|
||||
def get_system_account():
|
||||
try:
|
||||
from igny8_core.auth.models import Account
|
||||
for slug in ['aws-admin', 'default-account', 'default']:
|
||||
acct = Account.objects.filter(slug=slug).first()
|
||||
if acct:
|
||||
return acct
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
def get_integration_key(integration_type: str, account):
|
||||
if not account:
|
||||
return None
|
||||
try:
|
||||
from igny8_core.modules.system.models import IntegrationSettings
|
||||
|
||||
# Load OpenAI settings
|
||||
openai_settings = IntegrationSettings.objects.filter(
|
||||
integration_type='openai',
|
||||
account=self.account,
|
||||
settings_obj = IntegrationSettings.objects.filter(
|
||||
integration_type=integration_type,
|
||||
account=account,
|
||||
is_active=True
|
||||
).first()
|
||||
if openai_settings and openai_settings.config:
|
||||
self._openai_api_key = openai_settings.config.get('apiKey')
|
||||
|
||||
# Load Runware settings
|
||||
runware_settings = IntegrationSettings.objects.filter(
|
||||
integration_type='runware',
|
||||
account=self.account,
|
||||
is_active=True
|
||||
).first()
|
||||
if runware_settings and runware_settings.config:
|
||||
self._runware_api_key = runware_settings.config.get('apiKey')
|
||||
if settings_obj and settings_obj.config:
|
||||
return settings_obj.config.get('apiKey')
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load account settings: {e}", exc_info=True)
|
||||
|
||||
# Fallback to Django settings for API keys only (no model fallback)
|
||||
logger.warning(f"Could not load {integration_type} settings for account {getattr(account, 'id', None)}: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
# 1) Account-specific keys
|
||||
if self.account:
|
||||
self._openai_api_key = get_integration_key('openai', self.account)
|
||||
self._runware_api_key = get_integration_key('runware', self.account)
|
||||
|
||||
# 2) Fallback to system account keys (shared across tenants)
|
||||
if not self._openai_api_key or not self._runware_api_key:
|
||||
system_account = get_system_account()
|
||||
if not self._openai_api_key:
|
||||
self._openai_api_key = get_integration_key('openai', system_account)
|
||||
if not self._runware_api_key:
|
||||
self._runware_api_key = get_integration_key('runware', system_account)
|
||||
|
||||
# 3) Fallback to Django settings
|
||||
if not self._openai_api_key:
|
||||
self._openai_api_key = getattr(settings, 'OPENAI_API_KEY', None)
|
||||
if not self._runware_api_key:
|
||||
|
||||
@@ -160,3 +160,21 @@ class IsAdminOrOwner(permissions.BasePermission):
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -41,9 +41,11 @@ class DebugScopedRateThrottle(ScopedRateThrottle):
|
||||
if not request.user or not hasattr(request.user, 'is_authenticated') or not request.user.is_authenticated:
|
||||
public_blueprint_bypass = True
|
||||
|
||||
# Bypass for system account users (aws-admin, default-account, etc.)
|
||||
# Bypass for authenticated users (avoid user-facing 429s) and system accounts
|
||||
system_account_bypass = False
|
||||
authenticated_bypass = False
|
||||
if hasattr(request, 'user') and request.user and hasattr(request.user, 'is_authenticated') and request.user.is_authenticated:
|
||||
authenticated_bypass = True # Do not throttle logged-in users
|
||||
try:
|
||||
# Check if user is in system account (aws-admin, default-account, default)
|
||||
if hasattr(request.user, 'is_system_account_user') and request.user.is_system_account_user():
|
||||
@@ -55,7 +57,7 @@ class DebugScopedRateThrottle(ScopedRateThrottle):
|
||||
# If checking fails, continue with normal throttling
|
||||
pass
|
||||
|
||||
if debug_bypass or env_bypass or system_account_bypass or public_blueprint_bypass:
|
||||
if debug_bypass or env_bypass or system_account_bypass or public_blueprint_bypass or authenticated_bypass:
|
||||
# In debug mode or for system accounts, still set throttle headers but don't actually throttle
|
||||
# This allows testing throttle headers without blocking requests
|
||||
if hasattr(self, 'get_rate'):
|
||||
|
||||
@@ -10,7 +10,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
from igny8_core.api.base import AccountModelViewSet
|
||||
from igny8_core.api.response import success_response, error_response
|
||||
from igny8_core.api.throttles import DebugScopedRateThrottle
|
||||
from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess, IsAdminOrOwner
|
||||
from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess, IsSystemAccountOrDeveloper
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -30,7 +30,7 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
||||
Following reference plugin pattern: WordPress uses update_option() for igny8_api_settings
|
||||
We store in IntegrationSettings model with account isolation
|
||||
"""
|
||||
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess, IsAdminOrOwner]
|
||||
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess, IsSystemAccountOrDeveloper]
|
||||
|
||||
throttle_scope = 'system_admin'
|
||||
throttle_classes = [DebugScopedRateThrottle]
|
||||
|
||||
@@ -214,7 +214,8 @@ REST_FRAMEWORK = {
|
||||
'rest_framework.filters.OrderingFilter',
|
||||
],
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.AllowAny', # Allow unauthenticated access for now
|
||||
'igny8_core.api.permissions.IsAuthenticatedAndActive',
|
||||
'igny8_core.api.permissions.HasTenantAccess',
|
||||
],
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'igny8_core.api.authentication.APIKeyAuthentication', # WordPress API key authentication (check first)
|
||||
@@ -232,33 +233,33 @@ REST_FRAMEWORK = {
|
||||
'igny8_core.api.throttles.DebugScopedRateThrottle',
|
||||
],
|
||||
'DEFAULT_THROTTLE_RATES': {
|
||||
# AI Functions - Expensive operations
|
||||
'ai_function': '10/min', # AI content generation, clustering
|
||||
'image_gen': '15/min', # Image generation
|
||||
# AI Functions - Expensive operations (kept modest but higher to reduce false 429s)
|
||||
'ai_function': '60/min',
|
||||
'image_gen': '90/min',
|
||||
# Content Operations
|
||||
'content_write': '30/min', # Content creation, updates
|
||||
'content_read': '100/min', # Content listing, retrieval
|
||||
'content_write': '180/min',
|
||||
'content_read': '600/min',
|
||||
# Authentication
|
||||
'auth': '20/min', # Login, register, password reset
|
||||
'auth_strict': '5/min', # Sensitive auth operations
|
||||
'auth_read': '120/min', # Read-only auth-adjacent endpoints (e.g., subscriptions)
|
||||
'auth': '300/min', # Login, register, password reset
|
||||
'auth_strict': '120/min', # Sensitive auth operations
|
||||
'auth_read': '600/min', # Read-only auth-adjacent endpoints (e.g., subscriptions, industries)
|
||||
# Planner Operations
|
||||
'planner': '60/min', # Keyword, cluster, idea operations
|
||||
'planner_ai': '10/min', # AI-powered planner operations
|
||||
'planner': '300/min',
|
||||
'planner_ai': '60/min',
|
||||
# Writer Operations
|
||||
'writer': '60/min', # Task, content management
|
||||
'writer_ai': '10/min', # AI-powered writer operations
|
||||
'writer': '300/min',
|
||||
'writer_ai': '60/min',
|
||||
# System Operations
|
||||
'system': '100/min', # Settings, prompts, profiles
|
||||
'system_admin': '30/min', # Admin-only system operations
|
||||
'system': '600/min',
|
||||
'system_admin': '120/min',
|
||||
# Billing Operations
|
||||
'billing': '30/min', # Credit queries, usage logs
|
||||
'billing_admin': '10/min', # Credit management (admin)
|
||||
'linker': '30/min', # Content linking operations
|
||||
'optimizer': '10/min', # AI-powered optimization
|
||||
'integration': '100/min', # Integration operations (WordPress, etc.)
|
||||
'billing': '180/min',
|
||||
'billing_admin': '60/min',
|
||||
'linker': '180/min',
|
||||
'optimizer': '60/min',
|
||||
'integration': '600/min',
|
||||
# Default fallback
|
||||
'default': '100/min', # Default for endpoints without scope
|
||||
'default': '600/min',
|
||||
},
|
||||
# OpenAPI Schema Generation (drf-spectacular)
|
||||
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||
|
||||
Reference in New Issue
Block a user