Files
CyberPanel/aiScanner/aiScannerManager.py

679 lines
32 KiB
Python
Raw Normal View History

2025-06-25 19:27:36 +05:00
import requests
import json
import uuid
import secrets
from datetime import datetime, timedelta
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.utils import timezone
from django.conf import settings
from django.urls import reverse
from django.contrib import messages
from loginSystem.models import Administrator
from .models import AIScannerSettings, ScanHistory, FileAccessToken
from plogical.acl import ACLManager
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
class AIScannerManager:
AI_SCANNER_API_BASE = 'https://platform.cyberpersons.com/ai-scanner'
def __init__(self):
self.logger = logging
def scannerHome(self, request, userID):
"""Main AI Scanner page"""
try:
admin = Administrator.objects.get(pk=userID)
# Check ACL permissions (with fallback for new field)
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL.aiScannerAccess == 0:
return ACLManager.loadError()
except AttributeError:
# Field doesn't exist yet, allow access for now
self.logger.writeToFile(f'[AIScannerManager.scannerHome] aiScannerAccess field not found, allowing access')
pass
# Get or create scanner settings
scanner_settings, created = AIScannerSettings.objects.get_or_create(
admin=admin,
defaults={'balance': 0.0000, 'is_payment_configured': False}
)
# Get current pricing from API
pricing_data = self.get_ai_scanner_pricing()
# Get recent scan history
recent_scans = ScanHistory.objects.filter(admin=admin)[:10]
# Get current balance if payment is configured
current_balance = scanner_settings.balance
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Stored balance: {current_balance}')
if scanner_settings.is_payment_configured:
# Try to fetch latest balance from API (now supports flexible auth)
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Fetching balance from API...')
api_balance = self.get_account_balance(scanner_settings.api_key)
self.logger.writeToFile(f'[AIScannerManager.scannerHome] API returned balance: {api_balance}')
if api_balance is not None:
scanner_settings.balance = api_balance
scanner_settings.save()
current_balance = api_balance
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Updated balance to: {current_balance}')
else:
self.logger.writeToFile(f'[AIScannerManager.scannerHome] API balance call failed, keeping stored balance: {current_balance}')
# Get user's websites for scan selection
from websiteFunctions.models import Websites
try:
websites = Websites.objects.filter(admin=admin)
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Found {websites.count()} websites for {admin.userName}')
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Error fetching websites: {str(e)}')
websites = []
# Build context safely
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Building context for {admin.userName}')
context = {
'admin': admin,
'scanner_settings': scanner_settings,
'pricing_data': pricing_data,
'recent_scans': recent_scans,
'current_balance': current_balance,
'websites': websites,
'is_payment_configured': scanner_settings.is_payment_configured,
}
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Context built successfully, rendering template')
return render(request, 'aiScanner/scanner.html', context)
except Exception as e:
import traceback
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Error: {str(e)}')
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Traceback: {traceback.format_exc()}')
return render(request, 'aiScanner/scanner.html', {
'error': f'Failed to load AI Scanner page: {str(e)}',
'is_payment_configured': False,
'websites': [],
'recent_scans': [],
'current_balance': 0,
'pricing_data': None
})
def setupPayment(self, request, userID):
"""Setup payment method for AI scanner"""
try:
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request method'})
admin = Administrator.objects.get(pk=userID)
# Check ACL permissions (with fallback for new field)
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL.aiScannerAccess == 0:
return JsonResponse({'success': False, 'error': 'Access denied'})
except AttributeError:
# Field doesn't exist yet, allow access for now
pass
# Get admin email and domain
cyberpanel_host = request.get_host() # Keep full host including port
cyberpanel_domain = cyberpanel_host.split(':')[0] # Domain only for email fallback
admin_email = admin.email if hasattr(admin, 'email') and admin.email else f'{admin.userName}@{cyberpanel_domain}'
self.logger.writeToFile(f'[AIScannerManager.setupPayment] Admin: {admin.userName}, Email: {admin_email}, Host: {cyberpanel_host}')
# Setup payment with AI Scanner API
self.logger.writeToFile(f'[AIScannerManager.setupPayment] Attempting payment setup for {admin_email} on {cyberpanel_host}')
setup_data = self.setup_ai_scanner_payment(admin_email, cyberpanel_host)
if setup_data:
self.logger.writeToFile(f'[AIScannerManager.setupPayment] Payment setup successful for {admin_email}')
return JsonResponse({
'success': True,
'payment_url': setup_data['payment_url'],
'token': setup_data['token']
})
else:
self.logger.writeToFile(f'[AIScannerManager.setupPayment] Payment setup failed for {admin_email}')
return JsonResponse({
'success': False,
'error': 'Failed to setup payment. Please check the logs and try again.'
})
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.setupPayment] Error: {str(e)}')
return JsonResponse({'success': False, 'error': 'Internal server error'})
def setupComplete(self, request, userID):
"""Handle return from payment setup"""
try:
admin = Administrator.objects.get(pk=userID)
status = request.GET.get('status')
# Log all URL parameters for debugging
self.logger.writeToFile(f'[AIScannerManager.setupComplete] All URL params: {dict(request.GET)}')
if status == 'success':
api_key = request.GET.get('api_key')
balance = request.GET.get('balance', '0.00')
self.logger.writeToFile(f'[AIScannerManager.setupComplete] API Key: {api_key[:20] if api_key else "None"}...')
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Balance from URL: {balance}')
if api_key:
# Update scanner settings
scanner_settings, created = AIScannerSettings.objects.get_or_create(
admin=admin,
defaults={
'api_key': api_key,
'balance': float(balance),
'is_payment_configured': True
}
)
if not created:
scanner_settings.api_key = api_key
scanner_settings.balance = float(balance)
scanner_settings.is_payment_configured = True
scanner_settings.save()
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Saved balance: {scanner_settings.balance}')
messages.success(request, f'Payment setup successful! You have ${balance} credit.')
self.logger.writeToFile(f'[AIScannerManager] Payment setup completed for {admin.userName} with balance ${balance}')
else:
messages.error(request, 'Payment setup completed but API key not received.')
elif status in ['failed', 'cancelled', 'error']:
error = request.GET.get('error', 'Payment setup failed')
messages.error(request, f'Payment setup failed: {error}')
self.logger.writeToFile(f'[AIScannerManager] Payment setup failed for {admin.userName}: {error}')
return redirect('aiScannerHome')
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Error: {str(e)}')
messages.error(request, 'An error occurred during payment setup.')
return redirect('aiScannerHome')
def startScan(self, request, userID):
"""Start a new AI security scan"""
try:
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request method'})
admin = Administrator.objects.get(pk=userID)
# Check ACL permissions (with fallback for new field)
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL.aiScannerAccess == 0:
return JsonResponse({'success': False, 'error': 'Access denied'})
except AttributeError:
# Field doesn't exist yet, allow access for now
pass
# Get scanner settings
try:
scanner_settings = AIScannerSettings.objects.get(admin=admin)
if not scanner_settings.is_payment_configured or not scanner_settings.api_key:
return JsonResponse({'success': False, 'error': 'Payment not configured'})
except AIScannerSettings.DoesNotExist:
return JsonResponse({'success': False, 'error': 'Scanner not configured'})
# Parse request data
data = json.loads(request.body)
domain = data.get('domain')
scan_type = data.get('scan_type', 'full')
if not domain:
return JsonResponse({'success': False, 'error': 'Domain is required'})
# Validate domain belongs to user
from websiteFunctions.models import Websites
try:
website = Websites.objects.get(domain=domain, admin=admin)
except Websites.DoesNotExist:
return JsonResponse({'success': False, 'error': 'Domain not found or access denied'})
# Generate scan ID and file access token
scan_id = f'cp_{uuid.uuid4().hex[:12]}'
file_access_token = self.generate_file_access_token()
# Create scan history record
scan_history = ScanHistory.objects.create(
admin=admin,
scan_id=scan_id,
domain=domain,
scan_type=scan_type,
status='pending'
)
# Create file access token
FileAccessToken.objects.create(
token=file_access_token,
scan_history=scan_history,
domain=domain,
wp_path=f'/home/{domain}/public_html', # Adjust path as needed
expires_at=timezone.now() + timedelta(hours=2)
)
# Submit scan to AI Scanner API
callback_url = f"https://{request.get_host()}/api/ai-scanner/callback"
file_access_base_url = f"https://{request.get_host()}/api/ai-scanner/"
scan_response = self.submit_wordpress_scan(
scanner_settings.api_key,
domain,
scan_type,
callback_url,
file_access_token,
file_access_base_url,
scan_id
)
if scan_response:
scan_history.status = 'running'
scan_history.save()
return JsonResponse({
'success': True,
'scan_id': scan_id,
'message': 'Scan started successfully'
})
else:
scan_history.status = 'failed'
scan_history.error_message = 'Failed to submit scan to AI Scanner API'
scan_history.save()
return JsonResponse({'success': False, 'error': 'Failed to start scan'})
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.startScan] Error: {str(e)}')
return JsonResponse({'success': False, 'error': 'Internal server error'})
def refreshBalance(self, request, userID):
"""Refresh account balance from API"""
try:
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request method'})
admin = Administrator.objects.get(pk=userID)
# Get scanner settings
try:
scanner_settings = AIScannerSettings.objects.get(admin=admin)
if not scanner_settings.is_payment_configured or not scanner_settings.api_key:
return JsonResponse({'success': False, 'error': 'Payment not configured'})
except AIScannerSettings.DoesNotExist:
return JsonResponse({'success': False, 'error': 'Scanner not configured'})
# Fetch balance from API
api_balance = self.get_account_balance(scanner_settings.api_key)
if api_balance is not None:
old_balance = scanner_settings.balance
scanner_settings.balance = api_balance
scanner_settings.save()
self.logger.writeToFile(f'[AIScannerManager.refreshBalance] Updated balance from ${old_balance} to ${api_balance} for {admin.userName}')
return JsonResponse({
'success': True,
'balance': float(api_balance),
'message': f'Balance refreshed: ${api_balance:.4f}'
})
else:
return JsonResponse({'success': False, 'error': 'Failed to fetch balance from API'})
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.refreshBalance] Error: {str(e)}')
return JsonResponse({'success': False, 'error': 'Internal server error'})
def addPaymentMethod(self, request, userID):
"""Add a new payment method for the user"""
try:
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request method'})
admin = Administrator.objects.get(pk=userID)
# Check if user has scanner configured
try:
scanner_settings = AIScannerSettings.objects.get(admin=admin)
if not scanner_settings.is_payment_configured or not scanner_settings.api_key:
return JsonResponse({'success': False, 'error': 'Initial payment setup required first'})
except AIScannerSettings.DoesNotExist:
return JsonResponse({'success': False, 'error': 'Scanner not configured'})
# Get admin email and domain
cyberpanel_host = request.get_host() # Keep full host including port
cyberpanel_domain = cyberpanel_host.split(':')[0] # Domain only for email fallback
admin_email = admin.email if hasattr(admin, 'email') and admin.email else f'{admin.userName}@{cyberpanel_domain}'
self.logger.writeToFile(f'[AIScannerManager.addPaymentMethod] Setting up new payment method for {admin.userName} (API key authentication)')
# Call platform API to add payment method
setup_data = self.setup_add_payment_method(scanner_settings.api_key, admin_email, cyberpanel_host)
if setup_data:
self.logger.writeToFile(f'[AIScannerManager.addPaymentMethod] Payment method setup successful for {admin_email}')
return JsonResponse({
'success': True,
'setup_url': setup_data['setup_url'],
'token': setup_data.get('token', '')
})
else:
self.logger.writeToFile(f'[AIScannerManager.addPaymentMethod] Payment method setup failed for {admin_email}')
return JsonResponse({
'success': False,
'error': 'Failed to setup payment method. Please try again.'
})
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.addPaymentMethod] Error: {str(e)}')
return JsonResponse({'success': False, 'error': 'Internal server error'})
def paymentMethodComplete(self, request, userID):
"""Handle return from adding payment method"""
try:
admin = Administrator.objects.get(pk=userID)
status = request.GET.get('status')
# Log all URL parameters for debugging
self.logger.writeToFile(f'[AIScannerManager.paymentMethodComplete] All URL params: {dict(request.GET)}')
if status == 'success':
payment_method_id = request.GET.get('payment_method_id')
card_last4 = request.GET.get('card_last4')
card_brand = request.GET.get('card_brand')
self.logger.writeToFile(f'[AIScannerManager.paymentMethodComplete] Payment method added: {payment_method_id} ({card_brand} ****{card_last4})')
if payment_method_id:
messages.success(request, f'Payment method added successfully! New {card_brand} card ending in {card_last4}.')
self.logger.writeToFile(f'[AIScannerManager] Payment method added for {admin.userName}: {card_brand} ****{card_last4}')
else:
messages.success(request, 'Payment method added successfully!')
elif status in ['failed', 'cancelled', 'error']:
error = request.GET.get('error', 'Failed to add payment method')
messages.error(request, f'Failed to add payment method: {error}')
self.logger.writeToFile(f'[AIScannerManager] Payment method add failed for {admin.userName}: {error}')
return redirect('aiScannerHome')
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.paymentMethodComplete] Error: {str(e)}')
messages.error(request, 'An error occurred while adding payment method.')
return redirect('aiScannerHome')
def scanCallback(self, request):
"""Handle scan results callback from AI Scanner API"""
try:
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request method'})
data = json.loads(request.body)
scan_id = data.get('scan_id')
status = data.get('status')
if not scan_id:
return JsonResponse({'success': False, 'error': 'Scan ID required'})
# Find scan history record
try:
scan_history = ScanHistory.objects.get(scan_id=scan_id)
except ScanHistory.DoesNotExist:
self.logger.writeToFile(f'[AIScannerManager.scanCallback] Scan not found: {scan_id}')
return JsonResponse({'success': False, 'error': 'Scan not found'})
# Update scan status and results
scan_history.status = status
scan_history.completed_at = timezone.now()
if status == 'completed':
findings = data.get('findings', [])
summary = data.get('summary', {})
cost_usd = data.get('cost_usd', 0)
files_scanned = data.get('files_scanned', 0)
scan_history.set_findings(findings)
scan_history.set_summary(summary)
scan_history.cost_usd = cost_usd
scan_history.files_scanned = files_scanned
scan_history.issues_found = len(findings)
# Update user balance
scanner_settings = scan_history.admin.ai_scanner_settings
if cost_usd and scanner_settings.balance >= cost_usd:
scanner_settings.balance -= cost_usd
scanner_settings.save()
self.logger.writeToFile(f'[AIScannerManager] Scan completed: {scan_id}, Cost: ${cost_usd}, Issues: {len(findings)}')
elif status == 'failed':
error_message = data.get('error', 'Scan failed')
scan_history.error_message = error_message
self.logger.writeToFile(f'[AIScannerManager] Scan failed: {scan_id}, Error: {error_message}')
scan_history.save()
# Deactivate file access tokens
FileAccessToken.objects.filter(scan_history=scan_history).update(is_active=False)
return JsonResponse({'success': True})
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.scanCallback] Error: {str(e)}')
return JsonResponse({'success': False, 'error': 'Internal server error'})
# API Helper Methods
def get_ai_scanner_pricing(self):
"""Get current pricing from AI Scanner API"""
try:
response = requests.get(f'{self.AI_SCANNER_API_BASE}/api/plan/', timeout=10)
if response.status_code == 200:
return response.json()
return None
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.get_ai_scanner_pricing] Error: {str(e)}')
return None
def setup_ai_scanner_payment(self, user_email, cyberpanel_host):
"""Setup payment method with AI Scanner API"""
try:
payload = {
'email': user_email,
'domain': cyberpanel_host.split(':')[0], # Send domain without port
'return_url': f'https://{cyberpanel_host}/aiscanner/setup-complete/' # Include port in URL
}
self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Sending request to: {self.AI_SCANNER_API_BASE}/cyberpanel/setup-payment/')
self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Payload: {payload}')
response = requests.post(
f'{self.AI_SCANNER_API_BASE}/cyberpanel/setup-payment/',
json=payload,
timeout=10
)
self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Response status: {response.status_code}')
self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Response content: {response.text}')
if response.status_code == 200:
data = response.json()
if data.get('success'):
return {
'payment_url': data['payment_url'],
'token': data['token']
}
else:
self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] API returned success=false: {data.get("error", "Unknown error")}')
else:
self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Non-200 status code: {response.status_code}')
return None
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.setup_ai_scanner_payment] Exception: {str(e)}')
return None
def get_account_balance(self, api_key):
"""Get current account balance"""
try:
self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Requesting balance from: {self.AI_SCANNER_API_BASE}/api/account/balance/')
response = requests.get(
f'{self.AI_SCANNER_API_BASE}/api/account/balance/',
headers={'X-API-Key': api_key},
timeout=10
)
self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Response status: {response.status_code}')
self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Response content: {response.text}')
if response.status_code == 200:
data = response.json()
if data.get('success'):
# Use the new balance_usd field from flexible API
balance = float(data.get('balance_usd', data.get('balance', 0)))
auth_method = data.get('authenticated_via', 'unknown')
self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Parsed balance: {balance} (auth: {auth_method})')
return balance
else:
# Even failed responses now include balance_usd hint
balance_hint = data.get('balance_usd', 0)
self.logger.writeToFile(f'[AIScannerManager.get_account_balance] API returned success=false: {data.get("error", "Unknown error")} (balance hint: {balance_hint})')
# Return the balance hint if available, even on auth failure
if balance_hint > 0:
return float(balance_hint)
else:
self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Non-200 status code: {response.status_code}')
return None
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.get_account_balance] Exception: {str(e)}')
return None
def submit_wordpress_scan(self, api_key, domain, scan_type, callback_url, file_access_token, file_access_base_url, scan_id):
"""Submit scan request to AI Scanner API"""
try:
payload = {
'site_url': domain,
'scan_type': scan_type,
'cyberpanel_callback': callback_url,
'file_access_token': file_access_token,
'file_access_base_url': file_access_base_url,
'scan_id': scan_id
}
self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Submitting scan {scan_id} for {domain}')
self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Payload: {payload}')
response = requests.post(
f'{self.AI_SCANNER_API_BASE}/api/scan/submit-v2/',
headers={'X-API-Key': api_key},
json=payload,
timeout=10
)
self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Response status: {response.status_code}')
self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Response content: {response.text}')
if response.status_code == 200:
data = response.json()
if data.get('success'):
platform_scan_id = data.get('scan_id')
self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Platform assigned scan ID: {platform_scan_id}')
return platform_scan_id
else:
self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Platform returned success=false: {data.get("error", "Unknown error")}')
else:
self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Non-200 status code: {response.status_code}')
return None
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.submit_wordpress_scan] Error: {str(e)}')
return None
def get_scan_status(self, api_key, scan_id):
"""Get scan status from AI Scanner API"""
try:
response = requests.get(
f'{self.AI_SCANNER_API_BASE}/api/scan/{scan_id}/status/',
headers={'X-API-Key': api_key},
timeout=10
)
if response.status_code == 200:
return response.json()
return None
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.get_scan_status] Error: {str(e)}')
return None
def get_scan_results(self, api_key, scan_id):
"""Get scan results from AI Scanner API"""
try:
response = requests.get(
f'{self.AI_SCANNER_API_BASE}/api/scan/{scan_id}/results/',
headers={'X-API-Key': api_key},
timeout=10
)
if response.status_code == 200:
return response.json()
return None
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.get_scan_results] Error: {str(e)}')
return None
def setup_add_payment_method(self, api_key, user_email, cyberpanel_host):
"""Setup additional payment method with AI Scanner API"""
try:
payload = {
'domain': cyberpanel_host.split(':')[0], # Send domain without port
'return_url': f'https://{cyberpanel_host}/aiscanner/payment-method-complete/', # Include port in URL
'action': 'add_payment_method' # Indicate this is adding a payment method, not initial setup
}
self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Sending request to: {self.AI_SCANNER_API_BASE}/cyberpanel/add-payment-method/')
self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Payload: {payload}')
response = requests.post(
f'{self.AI_SCANNER_API_BASE}/cyberpanel/add-payment-method/',
headers={'X-API-Key': api_key},
json=payload,
timeout=10
)
self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Response status: {response.status_code}')
self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Response content: {response.text}')
if response.status_code == 200:
data = response.json()
if data.get('success'):
return {
'setup_url': data['setup_url'],
'token': data.get('token', '')
}
else:
self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] API returned success=false: {data.get("error", "Unknown error")}')
else:
self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Non-200 status code: {response.status_code}')
return None
except Exception as e:
self.logger.writeToFile(f'[AIScannerManager.setup_add_payment_method] Exception: {str(e)}')
return None
def generate_file_access_token(self):
"""Generate secure file access token"""
return f'cp_{secrets.token_urlsafe(32)}'