mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-10-26 07:46:35 +01:00
918 lines
45 KiB
Python
918 lines
45 KiB
Python
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)
|
|
|
|
# Load ACL permissions
|
|
currentACL = ACLManager.loadedACL(userID)
|
|
|
|
# Check ACL permissions (with fallback for new field)
|
|
try:
|
|
if currentACL.get('aiScannerAccess', 1) == 0:
|
|
return ACLManager.loadError()
|
|
except (AttributeError, KeyError):
|
|
# 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 with ACL respect
|
|
if currentACL['admin'] == 1:
|
|
# Admin can see all scans
|
|
recent_scans = ScanHistory.objects.all().order_by('-started_at')[:10]
|
|
else:
|
|
# Users can only see their own scans and their sub-users' scans
|
|
user_admins = ACLManager.loadUserObjects(userID)
|
|
recent_scans = ScanHistory.objects.filter(admin__in=user_admins).order_by('-started_at')[: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}')
|
|
|
|
# Check VPS free scans availability
|
|
server_ip = ACLManager.fetchIP()
|
|
vps_info = self.check_vps_free_scans(server_ip)
|
|
|
|
# Get user's websites for scan selection using ACL-aware method
|
|
try:
|
|
websites = ACLManager.findWebsiteObjects(currentACL, userID)
|
|
self.logger.writeToFile(f'[AIScannerManager.scannerHome] Found {len(websites)} 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,
|
|
'vps_info': vps_info,
|
|
'server_ip': server_ip,
|
|
}
|
|
|
|
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)
|
|
|
|
# Load ACL permissions
|
|
currentACL = ACLManager.loadedACL(userID)
|
|
|
|
# Check ACL permissions (with fallback for new field)
|
|
try:
|
|
if currentACL.get('aiScannerAccess', 1) == 0:
|
|
return JsonResponse({'success': False, 'error': 'Access denied'})
|
|
except (AttributeError, KeyError):
|
|
# 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')
|
|
charged = request.GET.get('charged') == 'true'
|
|
amount = request.GET.get('amount', '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}')
|
|
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Charged: {charged}, Amount: {amount}')
|
|
|
|
if api_key:
|
|
try:
|
|
# Convert balance to float with error handling
|
|
balance_float = float(balance) if balance else 0.0
|
|
|
|
# Update scanner settings
|
|
scanner_settings, created = AIScannerSettings.objects.get_or_create(
|
|
admin=admin,
|
|
defaults={
|
|
'api_key': api_key,
|
|
'balance': balance_float,
|
|
'is_payment_configured': True
|
|
}
|
|
)
|
|
|
|
if not created:
|
|
# Update existing record
|
|
scanner_settings.api_key = api_key
|
|
scanner_settings.balance = balance_float
|
|
scanner_settings.is_payment_configured = True
|
|
scanner_settings.save()
|
|
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Updated existing scanner settings')
|
|
else:
|
|
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Created new scanner settings')
|
|
|
|
# Verify the save worked
|
|
scanner_settings.refresh_from_db()
|
|
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Final state - API Key: {scanner_settings.api_key[:20] if scanner_settings.api_key else "None"}..., Balance: {scanner_settings.balance}, Configured: {scanner_settings.is_payment_configured}')
|
|
|
|
# Success message
|
|
if charged:
|
|
messages.success(request, f'Payment setup successful! ${amount} charged to your card. You have ${balance} credit.')
|
|
else:
|
|
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}')
|
|
|
|
except ValueError as e:
|
|
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Balance conversion error: {str(e)}')
|
|
messages.error(request, 'Payment setup completed but balance format invalid.')
|
|
except Exception as e:
|
|
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Database save error: {str(e)}')
|
|
messages.error(request, 'Payment setup completed but failed to save settings.')
|
|
else:
|
|
self.logger.writeToFile(f'[AIScannerManager.setupComplete] No API key received in success callback')
|
|
messages.error(request, 'Payment setup completed but API key not received.')
|
|
|
|
elif status == 'partial_success':
|
|
# Handle partial success (payment method added but charge failed)
|
|
api_key = request.GET.get('api_key')
|
|
if api_key:
|
|
try:
|
|
scanner_settings, created = AIScannerSettings.objects.get_or_create(
|
|
admin=admin,
|
|
defaults={
|
|
'api_key': api_key,
|
|
'balance': 0.0,
|
|
'is_payment_configured': True
|
|
}
|
|
)
|
|
|
|
if not created:
|
|
scanner_settings.api_key = api_key
|
|
scanner_settings.is_payment_configured = True
|
|
scanner_settings.save()
|
|
|
|
messages.warning(request, 'Payment method added but initial charge failed. Please add funds manually.')
|
|
self.logger.writeToFile(f'[AIScannerManager] Partial payment setup for {admin.userName}')
|
|
except Exception as e:
|
|
self.logger.writeToFile(f'[AIScannerManager.setupComplete] Partial success save error: {str(e)}')
|
|
messages.error(request, 'Payment method setup partially failed.')
|
|
else:
|
|
messages.error(request, 'Payment method setup failed - no API key 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)
|
|
|
|
# Load ACL permissions
|
|
currentACL = ACLManager.loadedACL(userID)
|
|
|
|
# Check ACL permissions (with fallback for new field)
|
|
try:
|
|
if currentACL.get('aiScannerAccess', 1) == 0:
|
|
return JsonResponse({'success': False, 'error': 'Access denied'})
|
|
except (AttributeError, KeyError):
|
|
# Field doesn't exist yet, allow access for now
|
|
pass
|
|
|
|
# Check VPS free scans availability first
|
|
server_ip = ACLManager.fetchIP()
|
|
vps_info = self.check_vps_free_scans(server_ip)
|
|
|
|
# If VPS is eligible for free scans, get or create API key
|
|
vps_api_key = None
|
|
vps_key_data = None
|
|
if (vps_info.get('success') and
|
|
vps_info.get('is_vps') and
|
|
vps_info.get('free_scans_available', 0) > 0):
|
|
|
|
self.logger.writeToFile(f'[AIScannerManager.startScan] VPS eligible for free scans, getting API key for IP: {server_ip}')
|
|
vps_key_data = self.get_or_create_vps_api_key(server_ip)
|
|
|
|
if vps_key_data:
|
|
vps_api_key = vps_key_data.get('api_key')
|
|
free_scans_remaining = vps_key_data.get('free_scans_remaining', 0)
|
|
self.logger.writeToFile(f'[AIScannerManager.startScan] VPS API key obtained, {free_scans_remaining} free scans remaining')
|
|
else:
|
|
self.logger.writeToFile(f'[AIScannerManager.startScan] Failed to get VPS API key')
|
|
return JsonResponse({'success': False, 'error': 'Failed to authenticate VPS for free scans'})
|
|
|
|
# Get scanner settings (only required if not using VPS free scan)
|
|
scanner_settings = None
|
|
if not vps_api_key:
|
|
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 using ACL-aware method
|
|
from websiteFunctions.models import Websites
|
|
try:
|
|
# Check if user has access to this domain through ACL system
|
|
if not ACLManager.checkOwnership(domain, admin, currentACL):
|
|
return JsonResponse({'success': False, 'error': 'Access denied to this domain'})
|
|
|
|
# Get the website object (we know it exists due to checkOwnership)
|
|
website = Websites.objects.get(domain=domain)
|
|
except Websites.DoesNotExist:
|
|
return JsonResponse({'success': False, 'error': 'Domain not found'})
|
|
|
|
# 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/"
|
|
|
|
# Use VPS API key if available, otherwise use regular scanner settings
|
|
api_key_to_use = vps_api_key if vps_api_key else scanner_settings.api_key
|
|
|
|
scan_response = self.submit_wordpress_scan(
|
|
api_key_to_use,
|
|
domain,
|
|
scan_type,
|
|
callback_url,
|
|
file_access_token,
|
|
file_access_base_url,
|
|
scan_id,
|
|
server_ip
|
|
)
|
|
|
|
if scan_response:
|
|
scan_history.status = 'running'
|
|
scan_history.save()
|
|
|
|
# Create appropriate success message
|
|
if vps_api_key:
|
|
message = f'Free VPS scan started successfully! {vps_key_data.get("free_scans_remaining", 0)} free scans remaining.'
|
|
else:
|
|
message = 'Scan started successfully'
|
|
|
|
return JsonResponse({
|
|
'success': True,
|
|
'scan_id': scan_id,
|
|
'message': message
|
|
})
|
|
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)
|
|
|
|
# Load ACL permissions
|
|
currentACL = ACLManager.loadedACL(userID)
|
|
|
|
# Check ACL permissions (with fallback for new field)
|
|
try:
|
|
if currentACL.get('aiScannerAccess', 1) == 0:
|
|
return JsonResponse({'success': False, 'error': 'Access denied'})
|
|
except (AttributeError, KeyError):
|
|
# 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'})
|
|
|
|
# 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)
|
|
|
|
# Load ACL permissions
|
|
currentACL = ACLManager.loadedACL(userID)
|
|
|
|
# Check ACL permissions (with fallback for new field)
|
|
try:
|
|
if currentACL.get('aiScannerAccess', 1) == 0:
|
|
return JsonResponse({'success': False, 'error': 'Access denied'})
|
|
except (AttributeError, KeyError):
|
|
# Field doesn't exist yet, allow access for now
|
|
pass
|
|
|
|
# Check if user has scanner configured (create if VPS user)
|
|
try:
|
|
scanner_settings = AIScannerSettings.objects.get(admin=admin)
|
|
if not scanner_settings.is_payment_configured or not scanner_settings.api_key:
|
|
# Check if this is a VPS with free scans
|
|
server_ip = ACLManager.fetchIP()
|
|
vps_info = self.check_vps_free_scans(server_ip)
|
|
|
|
if vps_info.get('is_vps'):
|
|
# VPS users can add payment methods without initial setup
|
|
# Get or create VPS API key
|
|
vps_key_data = self.get_or_create_vps_api_key(server_ip)
|
|
if vps_key_data and vps_key_data.get('api_key'):
|
|
# Use VPS API key for adding payment method
|
|
api_key_to_use = vps_key_data.get('api_key')
|
|
else:
|
|
return JsonResponse({'success': False, 'error': 'Failed to authenticate VPS'})
|
|
else:
|
|
return JsonResponse({'success': False, 'error': 'Initial payment setup required first'})
|
|
else:
|
|
api_key_to_use = scanner_settings.api_key
|
|
except AIScannerSettings.DoesNotExist:
|
|
# Check if this is a VPS with free scans
|
|
server_ip = ACLManager.fetchIP()
|
|
vps_info = self.check_vps_free_scans(server_ip)
|
|
|
|
if vps_info.get('is_vps'):
|
|
# VPS users can add payment methods without initial setup
|
|
# Get or create VPS API key
|
|
vps_key_data = self.get_or_create_vps_api_key(server_ip)
|
|
if vps_key_data and vps_key_data.get('api_key'):
|
|
# Use VPS API key for adding payment method
|
|
api_key_to_use = vps_key_data.get('api_key')
|
|
else:
|
|
return JsonResponse({'success': False, 'error': 'Failed to authenticate VPS'})
|
|
else:
|
|
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(api_key_to_use, 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)
|
|
|
|
# Load ACL permissions
|
|
currentACL = ACLManager.loadedACL(userID)
|
|
|
|
# Check ACL permissions (with fallback for new field)
|
|
try:
|
|
if currentACL.get('aiScannerAccess', 1) == 0:
|
|
messages.error(request, 'Access denied to AI Scanner')
|
|
return redirect('dashboard')
|
|
except (AttributeError, KeyError):
|
|
# Field doesn't exist yet, allow access for now
|
|
pass
|
|
|
|
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, server_ip):
|
|
"""Submit scan request to AI Scanner API"""
|
|
try:
|
|
payload = {
|
|
'domain': domain,
|
|
'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,
|
|
'server_ip': server_ip
|
|
}
|
|
|
|
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 check_vps_free_scans(self, server_ip):
|
|
"""Check if server IP belongs to VPS hosting and has free scans available"""
|
|
try:
|
|
self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] Checking VPS free scans for IP: {server_ip}')
|
|
|
|
response = requests.post(
|
|
'https://platform.cyberpersons.com/ai-scanner/api/vps/check-free-scans/',
|
|
json={'ip': server_ip},
|
|
timeout=10
|
|
)
|
|
|
|
self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] Response status: {response.status_code}')
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] Response data: {data}')
|
|
return data
|
|
else:
|
|
self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] API error: {response.text}')
|
|
return {'success': False, 'is_vps': False, 'error': 'API call failed'}
|
|
except Exception as e:
|
|
self.logger.writeToFile(f'[AIScannerManager.check_vps_free_scans] Error: {str(e)}')
|
|
return {'success': False, 'is_vps': False, 'error': str(e)}
|
|
|
|
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 get_or_create_vps_api_key(self, server_ip):
|
|
"""Get API key for VPS free scans from platform"""
|
|
try:
|
|
payload = {'server_ip': server_ip}
|
|
|
|
self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Requesting VPS API key for IP: {server_ip}')
|
|
|
|
response = requests.post(
|
|
f'{self.AI_SCANNER_API_BASE}/api/vps/generate-api-key/',
|
|
json=payload,
|
|
headers={'Content-Type': 'application/json'},
|
|
timeout=10
|
|
)
|
|
|
|
self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Response status: {response.status_code}')
|
|
self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Response content: {response.text}')
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
if data.get('success'):
|
|
return {
|
|
'api_key': data.get('api_key'),
|
|
'free_scans_remaining': data.get('free_scans_remaining'),
|
|
'account_type': data.get('account_type'),
|
|
'vps_name': data.get('vps_name'),
|
|
'vps_id': data.get('vps_id')
|
|
}
|
|
else:
|
|
self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] API returned success=false: {data.get("error", "Unknown error")}')
|
|
else:
|
|
self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Non-200 status code: {response.status_code}')
|
|
|
|
return None
|
|
except Exception as e:
|
|
self.logger.writeToFile(f'[AIScannerManager.get_or_create_vps_api_key] Exception: {str(e)}')
|
|
return None
|
|
|
|
def generate_file_access_token(self):
|
|
"""Generate secure file access token"""
|
|
return f'cp_{secrets.token_urlsafe(32)}' |