mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-02-24 23:40:45 +01:00
Fix v2 API validation, error handling, and security hardening
- Use submitDBCreation/submitDBDeletion wrappers for database ops (adds package limits, uniqueness checks) - Check return values from email, FTP, DNS, SSL, PHP utilities - Run backup creation in multiprocessing.Process (non-blocking) - Fix vhost config filename from vhconf.conf to vhost.conf - Fix open_basedir detection to read actual vhost config file - Scope DNS record deletion to site's own zone - Add missing context vars (createWebsite, ipAddress) - Add error handling to v2Fetch and log viewer component - Add config_type and lines input validation - Fix FTP form hidden input binding issue - Add DeleteDocRoot key for domain deletion - Cast isAlias/ssl to int for domain creation
This commit is contained in:
@@ -34,6 +34,9 @@ async function v2Fetch(url, options = {}) {
|
||||
config.body = JSON.stringify(options.body);
|
||||
}
|
||||
const resp = await fetch(url, config);
|
||||
if (!resp.ok) {
|
||||
return { status: 0, error_message: 'Server error (' + resp.status + ')' };
|
||||
}
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
@@ -187,16 +190,20 @@ function logViewerComponent(apiUrl) {
|
||||
logType: 'access',
|
||||
lines: 100,
|
||||
loading: false,
|
||||
error: '',
|
||||
|
||||
async load() {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
try {
|
||||
const data = await v2Fetch(apiUrl + '?type=' + this.logType + '&lines=' + this.lines);
|
||||
if (data.status === 1) {
|
||||
this.logs = data.logs || [];
|
||||
} else {
|
||||
this.error = data.error_message || 'Failed to load logs';
|
||||
}
|
||||
} catch (e) {
|
||||
// silent
|
||||
this.error = 'Failed to load logs';
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
@@ -44,9 +44,8 @@
|
||||
<input type="password" class="v2-input" x-model="formData.ftpPassword" placeholder="Strong password">
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" x-model="formData.ftpDomain" value="{{ website.domain }}">
|
||||
<div style="display:flex; gap:8px; margin-top:8px;">
|
||||
<button class="v2-btn v2-btn-primary" @click="formData.ftpDomain = '{{ website.domain }}'; create(formData)">Create</button>
|
||||
<button class="v2-btn v2-btn-primary" @click="create(formData)">Create</button>
|
||||
<button class="v2-btn v2-btn-outline" @click="showForm = false">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<template x-if="error">
|
||||
<div class="v2-alert v2-alert-danger"><i class="fas fa-exclamation-circle"></i> <span x-text="error"></span></div>
|
||||
</template>
|
||||
|
||||
<!-- Log viewer -->
|
||||
<div class="v2-card">
|
||||
<div class="v2-card-header">
|
||||
|
||||
105
panelv2/views.py
105
panelv2/views.py
@@ -1,4 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from django.shortcuts import redirect, HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from plogical.httpProc import httpProc
|
||||
@@ -58,6 +60,17 @@ def _json(status, message, **extra):
|
||||
return HttpResponse(json.dumps(data), content_type='application/json')
|
||||
|
||||
|
||||
def _get_server_ip():
|
||||
"""Get the server's public IP address."""
|
||||
try:
|
||||
return ACLManager.GetServerIP()
|
||||
except:
|
||||
try:
|
||||
return socket.gethostbyname(socket.gethostname())
|
||||
except:
|
||||
return 'N/A'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Page views
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -69,9 +82,11 @@ def site_list(request):
|
||||
return _login_redirect()
|
||||
|
||||
websites = ACLManager.findWebsiteObjects(currentACL, userID)
|
||||
createWebsite = ACLManager.currentContextPermission(currentACL, 'createWebsite')
|
||||
data = {
|
||||
'site_context': None,
|
||||
'websites': websites,
|
||||
'createWebsite': createWebsite,
|
||||
}
|
||||
proc = httpProc(request, 'panelv2/site_list.html', data, 'listWebsites')
|
||||
return proc.render()
|
||||
@@ -343,6 +358,7 @@ def site_security(request, site_id):
|
||||
data = {
|
||||
'site_context': _site_context(website),
|
||||
'website': website,
|
||||
'ipAddress': _get_server_ip(),
|
||||
}
|
||||
proc = httpProc(request, 'panelv2/security.html', data, 'listWebsites')
|
||||
return proc.render()
|
||||
@@ -387,12 +403,13 @@ def api_databases(request, site_id):
|
||||
elif request.method == 'POST':
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
data['databaseWebsite'] = website.domain
|
||||
from plogical.mysqlUtilities import mysqlUtilities
|
||||
mysqlUtilities.createDatabase(data['dbName'], data['dbUser'], data['dbPassword'])
|
||||
newDB = Databases(website=website, dbName=data['dbName'], dbUser=data['dbUser'])
|
||||
newDB.save()
|
||||
return _json(1, 'None')
|
||||
result = mysqlUtilities.submitDBCreation(
|
||||
data['dbName'], data['dbUser'], data['dbPassword'], website.domain
|
||||
)
|
||||
if result[0] == 1:
|
||||
return _json(1, 'None')
|
||||
return _json(0, result[1])
|
||||
except BaseException as msg:
|
||||
return _json(0, str(msg))
|
||||
|
||||
@@ -401,9 +418,10 @@ def api_databases(request, site_id):
|
||||
data = json.loads(request.body)
|
||||
db = Databases.objects.get(pk=data['id'], website=website)
|
||||
from plogical.mysqlUtilities import mysqlUtilities
|
||||
mysqlUtilities.deleteDatabase(db.dbName, db.dbUser)
|
||||
db.delete()
|
||||
return _json(1, 'None')
|
||||
result = mysqlUtilities.submitDBDeletion(db.dbName)
|
||||
if result[0] == 1:
|
||||
return _json(1, 'None')
|
||||
return _json(0, result[1])
|
||||
except BaseException as msg:
|
||||
return _json(0, str(msg))
|
||||
|
||||
@@ -431,8 +449,10 @@ def api_email(request, site_id):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
from plogical.mailUtilities import mailUtilities
|
||||
mailUtilities.createEmailAccount(data['domain'], data['username'], data['password'])
|
||||
return _json(1, 'None')
|
||||
result = mailUtilities.createEmailAccount(data['domain'], data['username'], data['password'])
|
||||
if result[0] == 1:
|
||||
return _json(1, 'None')
|
||||
return _json(0, result[1])
|
||||
except BaseException as msg:
|
||||
return _json(0, str(msg))
|
||||
|
||||
@@ -440,8 +460,10 @@ def api_email(request, site_id):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
from plogical.mailUtilities import mailUtilities
|
||||
mailUtilities.deleteEmailAccount(data['email'])
|
||||
return _json(1, 'None')
|
||||
result = mailUtilities.deleteEmailAccount(data['email'])
|
||||
if result[0] == 1:
|
||||
return _json(1, 'None')
|
||||
return _json(0, result[1])
|
||||
except BaseException as msg:
|
||||
return _json(0, str(msg))
|
||||
|
||||
@@ -531,9 +553,12 @@ def api_dns(request, site_id):
|
||||
elif request.method == 'DELETE':
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
record = DNSRecords.objects.get(pk=data['id'])
|
||||
dns_domain = DNSDomains.objects.get(name=website.domain)
|
||||
record = DNSRecords.objects.get(pk=data['id'], domainOwner=dns_domain)
|
||||
record.delete()
|
||||
return _json(1, 'None')
|
||||
except DNSDomains.DoesNotExist:
|
||||
return _json(0, 'DNS zone not found for this domain')
|
||||
except BaseException as msg:
|
||||
return _json(0, str(msg))
|
||||
|
||||
@@ -592,12 +617,15 @@ def api_backup(request, site_id):
|
||||
elif request.method == 'POST':
|
||||
try:
|
||||
import time
|
||||
from multiprocessing import Process
|
||||
from plogical.backupUtilities import submitBackupCreation
|
||||
backupDomain = website.domain
|
||||
backupName = 'backup-%s-%s' % (backupDomain, time.strftime('%m.%d.%Y_%H-%M-%S'))
|
||||
backupPath = '/home/%s/backup' % backupDomain
|
||||
tempStoragePath = '%s/%s' % (backupPath, backupName)
|
||||
submitBackupCreation(tempStoragePath, backupName, backupPath, backupDomain)
|
||||
p = Process(target=submitBackupCreation,
|
||||
args=(tempStoragePath, backupName, backupPath, backupDomain))
|
||||
p.start()
|
||||
return _json(1, 'None')
|
||||
except BaseException as msg:
|
||||
return _json(0, str(msg))
|
||||
@@ -606,7 +634,6 @@ def api_backup(request, site_id):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
backup = Backups.objects.get(pk=data['id'], website=website)
|
||||
import os
|
||||
backup_file = '/home/%s/backup/%s' % (website.domain, backup.fileName)
|
||||
if os.path.exists(backup_file):
|
||||
os.remove(backup_file)
|
||||
@@ -630,7 +657,6 @@ def api_logs(request, site_id):
|
||||
return _json(0, 'Unauthorized or site not found')
|
||||
|
||||
if request.method == 'GET':
|
||||
import os
|
||||
log_type = request.GET.get('type', 'access')
|
||||
if log_type not in ('access', 'error'):
|
||||
log_type = 'access'
|
||||
@@ -638,6 +664,8 @@ def api_logs(request, site_id):
|
||||
lines = int(request.GET.get('lines', 50))
|
||||
except (ValueError, TypeError):
|
||||
lines = 50
|
||||
if lines < 1:
|
||||
lines = 50
|
||||
if lines > 1000:
|
||||
lines = 1000
|
||||
|
||||
@@ -650,8 +678,11 @@ def api_logs(request, site_id):
|
||||
if os.path.exists(log_path):
|
||||
try:
|
||||
from plogical.processUtilities import ProcessUtilities
|
||||
result = ProcessUtilities.outputExecutioner('tail -n %d %s' % (lines, log_path))
|
||||
log_lines = result.strip().split('\n') if result.strip() else []
|
||||
result = ProcessUtilities.outputExecutioner(
|
||||
'tail -n %d %s' % (lines, log_path)
|
||||
)
|
||||
if result and result.strip():
|
||||
log_lines = result.strip().split('\n')
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -673,10 +704,11 @@ def api_config(request, site_id):
|
||||
|
||||
if request.method == 'GET':
|
||||
config_type = request.GET.get('type', 'vhost')
|
||||
import os
|
||||
if config_type not in ('vhost', 'rewrite'):
|
||||
config_type = 'vhost'
|
||||
content = ''
|
||||
if config_type == 'vhost':
|
||||
vhost_path = '/usr/local/lsws/conf/vhosts/%s/vhconf.conf' % website.domain
|
||||
vhost_path = '/usr/local/lsws/conf/vhosts/%s/vhost.conf' % website.domain
|
||||
if os.path.exists(vhost_path):
|
||||
with open(vhost_path, 'r') as f:
|
||||
content = f.read()
|
||||
@@ -700,10 +732,12 @@ def api_config(request, site_id):
|
||||
elif config_type == 'php':
|
||||
from plogical.vhost import vhost
|
||||
vhFile = '/usr/local/lsws/conf/vhosts/%s/vhost.conf' % website.domain
|
||||
vhost.changePHP(vhFile, data['phpVersion'])
|
||||
website.phpSelection = data['phpVersion']
|
||||
website.save()
|
||||
return _json(1, 'None')
|
||||
result = vhost.changePHP(vhFile, data['phpVersion'])
|
||||
if result[0] == 1:
|
||||
website.phpSelection = data['phpVersion']
|
||||
website.save()
|
||||
return _json(1, 'None')
|
||||
return _json(0, str(result[1]))
|
||||
return _json(0, 'Unknown config type')
|
||||
except BaseException as msg:
|
||||
return _json(0, str(msg))
|
||||
@@ -737,8 +771,8 @@ def api_domains(request, site_id):
|
||||
'domainName': data['domain'],
|
||||
'path': data.get('path', ''),
|
||||
'phpSelection': data.get('php', website.phpSelection),
|
||||
'ssl': data.get('ssl', 0),
|
||||
'alias': data.get('isAlias', 0),
|
||||
'ssl': int(data.get('ssl', 0)),
|
||||
'alias': int(data.get('isAlias', 0)),
|
||||
'openBasedir': 1,
|
||||
}
|
||||
result = wm.submitDomainCreation(userID, create_data)
|
||||
@@ -756,7 +790,7 @@ def api_domains(request, site_id):
|
||||
child = ChildDomains.objects.get(pk=data['id'], master=website)
|
||||
from websiteFunctions.website import WebsiteManager
|
||||
wm = WebsiteManager()
|
||||
delete_data = {'websiteName': child.domain}
|
||||
delete_data = {'websiteName': child.domain, 'DeleteDocRoot': 0}
|
||||
result = wm.submitDomainDeletion(userID, delete_data)
|
||||
result_data = json.loads(result.content)
|
||||
if result_data.get('status') == 1:
|
||||
@@ -780,11 +814,18 @@ def api_security(request, site_id):
|
||||
return _json(0, 'Unauthorized or site not found')
|
||||
|
||||
if request.method == 'GET':
|
||||
try:
|
||||
config = json.loads(website.config) if website.config else {}
|
||||
except:
|
||||
config = {}
|
||||
open_basedir = config.get('openBasedir', 1)
|
||||
# Check open_basedir by reading the vhost config file
|
||||
open_basedir = 1 # default to enabled
|
||||
vhost_path = '/usr/local/lsws/conf/vhosts/%s/vhost.conf' % website.domain
|
||||
if os.path.exists(vhost_path):
|
||||
try:
|
||||
with open(vhost_path, 'r') as f:
|
||||
vhost_content = f.read()
|
||||
# If no php_admin_value open_basedir line exists, it's disabled
|
||||
if 'open_basedir' not in vhost_content:
|
||||
open_basedir = 0
|
||||
except:
|
||||
pass
|
||||
return _json(1, 'None', openBasedir=open_basedir)
|
||||
|
||||
elif request.method == 'POST':
|
||||
|
||||
Reference in New Issue
Block a user