mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-07 19:56:57 +02:00
Fix File Manager: file deletion, special chars, upload auth (Root FM)
- Fix delete for domain and Root File Manager: use sudo helper when lscpd/executioner fails (TOKEN/sendCommand issues) - Add safe-delete-path and safe-move-path helpers for base64 path handling - Add ACLManager.isPathInsideHome and isFilePathSafeForShell for path validation - Fix upload authorization for Root File Manager (domainName empty) - Harden outputExecutioner result checks to prevent 500 on None - Update Bootstrap CDN for CSP compatibility - Improve error display and a11y focus management in modals - Resolves #1670: files with special characters can be uploaded/deleted
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
<link rel="icon" type="image/png" href="{% static 'baseTemplate/assets/finalBase/favicon.png' %}">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{% static 'filemanager/images/fonts/css/font-awesome.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'filemanager/css/fileManager.css' %}">
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</div-->
|
||||
<ul class="nav mr-10">
|
||||
<li class="nav-item">
|
||||
<a onclick="return false;" ng-click="showUploadBox()" class="nav-link point-events" href="#"><i class="fa fa-upload" aria-hidden="true"></i> {% trans "Upload" %}</a>
|
||||
<a id="uploadTriggerBtn" onclick="return false;" ng-click="showUploadBox()" class="nav-link point-events" href="#"><i class="fa fa-upload" aria-hidden="true"></i> {% trans "Upload" %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a onclick="return false;" ng-click="showCreateFileModal()" class="nav-link point-events" href="#"><i class="fa fa-plus-square" aria-hidden="true"></i> {% trans "New File" %}</a>
|
||||
@@ -608,7 +608,7 @@
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
<!-- HTML Editor Include -->
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import base64
|
||||
|
||||
from django.shortcuts import HttpResponse
|
||||
import json
|
||||
@@ -160,12 +161,67 @@ class FileManager:
|
||||
def returnPathEnclosed(self, path):
|
||||
return "'" + path + "'"
|
||||
|
||||
def _moveViaPythonBase64(self, src_path, dest_path, user):
|
||||
"""Fallback: use helper script or Python to move when mv fails (handles special chars)."""
|
||||
try:
|
||||
import subprocess
|
||||
s_b64 = base64.b64encode(src_path.encode('utf-8')).decode('ascii')
|
||||
d_b64 = base64.b64encode(dest_path.encode('utf-8')).decode('ascii')
|
||||
helper = '/usr/local/CyberCP/bin/safe-move-path'
|
||||
if os.path.isfile(helper) and os.access(helper, os.X_OK):
|
||||
cmd = [helper, s_b64, d_b64]
|
||||
if os.getuid() != 0:
|
||||
cmd = ['sudo', '-n'] + cmd
|
||||
try:
|
||||
res = subprocess.run(cmd, capture_output=True, timeout=30)
|
||||
if res.returncode == 0:
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"_moveViaPythonBase64 sudo helper failed: {str(e)}")
|
||||
command = '%s %s %s' % (helper, s_b64, d_b64)
|
||||
else:
|
||||
code = "import shutil,base64,sys; s=base64.b64decode(sys.argv[1]).decode(); d=base64.b64decode(sys.argv[2]).decode(); shutil.move(s,d)"
|
||||
command = "/usr/bin/python3 -c '%s' %s %s" % (code, s_b64, d_b64)
|
||||
result = ProcessUtilities.executioner(command, user)
|
||||
return result == 1
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"_moveViaPythonBase64 failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def _deleteViaPythonBase64(self, path, user):
|
||||
"""Fallback: use helper script or Python to delete when rm fails (handles special chars)."""
|
||||
try:
|
||||
import subprocess
|
||||
p_b64 = base64.b64encode(path.encode('utf-8')).decode('ascii')
|
||||
helper = '/usr/local/CyberCP/bin/safe-delete-path'
|
||||
if os.path.isfile(helper) and os.access(helper, os.X_OK):
|
||||
cmd = [helper, p_b64]
|
||||
if os.getuid() != 0:
|
||||
cmd = ['sudo', '-n'] + cmd
|
||||
try:
|
||||
res = subprocess.run(cmd, capture_output=True, timeout=30)
|
||||
if res.returncode == 0:
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"_deleteViaPythonBase64 sudo helper failed: {str(e)}")
|
||||
if os.path.isfile(helper) and os.access(helper, os.X_OK):
|
||||
command = '%s %s' % (helper, p_b64)
|
||||
else:
|
||||
code = "import os,base64,sys,shutil; p=base64.b64decode(sys.argv[1]).decode(); (os.path.isfile(p) and os.remove(p)) or (os.path.isdir(p) and shutil.rmtree(p))"
|
||||
command = "/usr/bin/python3 -c '%s' %s" % (code, p_b64)
|
||||
result = ProcessUtilities.executioner(command, user)
|
||||
return result == 1
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"_deleteViaPythonBase64 failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def changeOwner(self, path):
|
||||
try:
|
||||
domainName = self.data['domainName']
|
||||
website = Websites.objects.get(domain=domainName)
|
||||
homePath = '/home/%s' % (domainName)
|
||||
|
||||
if path.find('..') > -1:
|
||||
if not ACLManager.isPathInsideHome(path, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = "chown -R " + website.externalApp + ':' + website.externalApp + ' ' + self.returnPathEnclosed(path)
|
||||
@@ -186,8 +242,7 @@ class FileManager:
|
||||
|
||||
pathCheck = '/home/%s' % (domainName)
|
||||
|
||||
if self.data['completeStartingPath'].find(pathCheck) == -1 or self.data['completeStartingPath'].find(
|
||||
'..') > -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['completeStartingPath'], pathCheck):
|
||||
return self.ajaxPre(0, 'Not allowed to browse this path, going back home!')
|
||||
|
||||
command = "ls -la --group-directories-first " + self.returnPathEnclosed(
|
||||
@@ -197,8 +252,7 @@ class FileManager:
|
||||
except:
|
||||
pathCheck = '/'
|
||||
|
||||
if self.data['completeStartingPath'].find(pathCheck) == -1 or self.data['completeStartingPath'].find(
|
||||
'..') > -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['completeStartingPath'], pathCheck):
|
||||
return self.ajaxPre(0, 'Not allowed to browse this path, going back home!')
|
||||
|
||||
command = "ls -la --group-directories-first " + self.returnPathEnclosed(
|
||||
@@ -314,7 +368,7 @@ class FileManager:
|
||||
website = Websites.objects.get(domain=domainName)
|
||||
homePath = '/home/%s' % (domainName)
|
||||
|
||||
if self.data['fileName'].find('..') > -1 or self.data['fileName'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['fileName'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = "touch " + self.returnPathEnclosed(self.data['fileName'])
|
||||
@@ -327,7 +381,7 @@ class FileManager:
|
||||
except:
|
||||
homePath = '/'
|
||||
|
||||
if self.data['fileName'].find('..') > -1 or self.data['fileName'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['fileName'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = "touch " + self.returnPathEnclosed(self.data['fileName'])
|
||||
@@ -349,7 +403,7 @@ class FileManager:
|
||||
|
||||
homePath = '/home/%s' % (domainName)
|
||||
|
||||
if self.data['folderName'].find('..') > -1 or self.data['folderName'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['folderName'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = "mkdir " + self.returnPathEnclosed(self.data['folderName'])
|
||||
@@ -363,7 +417,7 @@ class FileManager:
|
||||
except:
|
||||
homePath = '/'
|
||||
|
||||
if self.data['folderName'].find('..') > -1 or self.data['folderName'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['folderName'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = "mkdir " + self.returnPathEnclosed(self.data['folderName'])
|
||||
@@ -392,25 +446,27 @@ class FileManager:
|
||||
website = Websites.objects.get(domain=domainName)
|
||||
self.homePath = '/home/%s' % (domainName)
|
||||
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Attempting to delete files/folders for domain: {domainName}")
|
||||
logging.writeToFile(f"Attempting to delete files/folders for domain: {domainName}")
|
||||
|
||||
RemoveOK = 1
|
||||
|
||||
# Test if directory is writable
|
||||
command = 'touch %s/public_html/hello.txt' % (self.homePath)
|
||||
result = ProcessUtilities.outputExecutioner(command)
|
||||
if result is None:
|
||||
result = ''
|
||||
|
||||
if result.find('cannot touch') > -1:
|
||||
if isinstance(result, (str, bytes)) and ('cannot touch' in str(result) or 'Permission denied' in str(result)):
|
||||
RemoveOK = 0
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Directory {self.homePath} is not writable, removing chattr flags")
|
||||
logging.writeToFile(f"Directory {self.homePath} is not writable, removing chattr flags")
|
||||
|
||||
# Remove immutable flag from entire directory
|
||||
# Remove immutable flag from entire directory (executioner returns 1=success, 0=failure)
|
||||
command = 'chattr -R -i %s' % (self.homePath)
|
||||
result = ProcessUtilities.executioner(command)
|
||||
if result.find('cannot') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to remove chattr -i from {self.homePath}: {result}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Warning: Failed to remove chattr -i from {self.homePath}: {result}")
|
||||
else:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully removed chattr -i from {self.homePath}")
|
||||
logging.writeToFile(f"Successfully removed chattr -i from {self.homePath}")
|
||||
|
||||
else:
|
||||
command = 'rm -f %s/public_html/hello.txt' % (self.homePath)
|
||||
@@ -421,24 +477,22 @@ class FileManager:
|
||||
itemPath = self.data['path'] + '/' + item
|
||||
|
||||
# Security check - prevent path traversal
|
||||
if itemPath.find('..') > -1 or itemPath.find(self.homePath) == -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Security violation: Attempted to delete outside home directory: {itemPath}")
|
||||
if not ACLManager.isPathInsideHome(itemPath, self.homePath):
|
||||
logging.writeToFile(f"Security violation: Attempted to delete outside home directory: {itemPath}")
|
||||
return self.ajaxPre(0, 'Not allowed to delete files outside home directory!')
|
||||
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Deleting: {itemPath}")
|
||||
logging.writeToFile(f"Deleting: {itemPath}")
|
||||
|
||||
if skipTrash:
|
||||
# Permanent deletion
|
||||
# Permanent deletion (executioner returns 1=success, 0=failure)
|
||||
command = 'rm -rf ' + self.returnPathEnclosed(itemPath)
|
||||
result = ProcessUtilities.executioner(command, website.externalApp)
|
||||
if result.find('cannot') > -1 or result.find('Permission denied') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Failed to delete {itemPath}: {result}")
|
||||
# Try with sudo if available
|
||||
command = 'sudo rm -rf ' + self.returnPathEnclosed(itemPath)
|
||||
result = ProcessUtilities.executioner(command, website.externalApp)
|
||||
if result.find('cannot') > -1 or result.find('Permission denied') > -1:
|
||||
return self.ajaxPre(0, f'Failed to delete {item}: {result}')
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully deleted: {itemPath}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Failed to delete {itemPath}: result={result}, trying Python fallback")
|
||||
# Fallback: Python+base64 to handle special chars in paths
|
||||
if not self._deleteViaPythonBase64(itemPath, website.externalApp):
|
||||
return self.ajaxPre(0, f'Failed to delete {item}')
|
||||
logging.writeToFile(f"Successfully deleted: {itemPath}")
|
||||
|
||||
## Update disk usage in background
|
||||
command = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/IncScheduler.py UpdateDiskUsageForceDomain --domainName %s" % (domainName)
|
||||
@@ -447,46 +501,44 @@ class FileManager:
|
||||
# Move to trash
|
||||
trashPath = '%s/.trash' % (self.homePath)
|
||||
|
||||
# Ensure trash directory exists
|
||||
# Ensure trash directory exists (executioner returns 1=success, 0=failure)
|
||||
command = 'mkdir -p %s' % (trashPath)
|
||||
result = ProcessUtilities.executioner(command, website.externalApp)
|
||||
if result.find('cannot') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Failed to create trash directory: {result}")
|
||||
return self.ajaxPre(0, f'Failed to create trash directory: {result}')
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Failed to create trash directory: result={result}")
|
||||
return self.ajaxPre(0, f'Failed to create trash directory')
|
||||
|
||||
# Save to trash database
|
||||
try:
|
||||
Trash(website=website, originalPath=self.returnPathEnclosed(self.data['path']),
|
||||
fileName=self.returnPathEnclosed(item)).save()
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Failed to save trash record: {str(e)}")
|
||||
logging.writeToFile(f"Failed to save trash record: {str(e)}")
|
||||
|
||||
# Move to trash
|
||||
# Move to trash (executioner returns 1=success, 0=failure)
|
||||
command = 'mv %s %s' % (self.returnPathEnclosed(itemPath), trashPath)
|
||||
result = ProcessUtilities.executioner(command, website.externalApp)
|
||||
if result.find('cannot') > -1 or result.find('Permission denied') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Failed to move to trash {itemPath}: {result}")
|
||||
# Try with sudo if available
|
||||
command = 'sudo mv %s %s' % (self.returnPathEnclosed(itemPath), trashPath)
|
||||
result = ProcessUtilities.executioner(command, website.externalApp)
|
||||
if result.find('cannot') > -1 or result.find('Permission denied') > -1:
|
||||
return self.ajaxPre(0, f'Failed to move {item} to trash: {result}')
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully moved to trash: {itemPath}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Failed to move to trash {itemPath}: result={result}, trying Python fallback")
|
||||
# Fallback: Python+base64 to handle special chars in paths (e.g. lscpd/sendCommand)
|
||||
if not self._moveViaPythonBase64(itemPath, trashPath, website.externalApp):
|
||||
return self.ajaxPre(0, f'Failed to move {item} to trash')
|
||||
logging.writeToFile(f"Successfully moved to trash: {itemPath}")
|
||||
|
||||
## Update disk usage in background
|
||||
command = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/IncScheduler.py UpdateDiskUsageForceDomain --domainName %s" % (domainName)
|
||||
ProcessUtilities.popenExecutioner(command)
|
||||
|
||||
if RemoveOK == 0:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Restoring chattr +i flags for {self.homePath}")
|
||||
logging.writeToFile(f"Restoring chattr +i flags for {self.homePath}")
|
||||
|
||||
# Restore immutable flag to entire directory
|
||||
# Restore immutable flag to entire directory (executioner returns 1=success, 0=failure)
|
||||
command = 'chattr -R +i %s' % (self.homePath)
|
||||
result = ProcessUtilities.executioner(command)
|
||||
if result.find('cannot') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to restore chattr +i to {self.homePath}: {result}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Warning: Failed to restore chattr +i to {self.homePath}: result={result}")
|
||||
else:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully restored chattr +i to {self.homePath}")
|
||||
logging.writeToFile(f"Successfully restored chattr +i to {self.homePath}")
|
||||
|
||||
# Allow specific directories to remain mutable
|
||||
mutable_dirs = ['/logs/', '/.trash/', '/backup/', '/incbackup/', '/lscache/', '/.cagefs/']
|
||||
@@ -494,68 +546,74 @@ class FileManager:
|
||||
dir_path = self.homePath + dir_name
|
||||
command = 'chattr -R -i %s' % (dir_path)
|
||||
result = ProcessUtilities.executioner(command)
|
||||
if result.find('cannot') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to remove chattr +i from {dir_path}: {result}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Warning: Failed to remove chattr +i from {dir_path}: result={result}")
|
||||
else:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully removed chattr +i from {dir_path}")
|
||||
logging.writeToFile(f"Successfully removed chattr +i from {dir_path}")
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Error in deleteFolderOrFile for {domainName}: {str(e)}")
|
||||
import traceback
|
||||
logging.writeToFile(f"Error in deleteFolderOrFile for {domainName}: {str(e)}")
|
||||
logging.writeToFile(traceback.format_exc())
|
||||
try:
|
||||
skipTrash = self.data['skipTrash']
|
||||
except:
|
||||
skipTrash = False
|
||||
|
||||
|
||||
# Fallback to root path for system files
|
||||
# Fallback to root path for system files (Root File Manager, domainName empty)
|
||||
self.homePath = '/'
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Using fallback deletion for system files in {self.data['path']}")
|
||||
logging.writeToFile(f"Using fallback deletion for system files in {self.data['path']}")
|
||||
|
||||
RemoveOK = 1
|
||||
|
||||
# Test if directory is writable
|
||||
command = 'touch %s/public_html/hello.txt' % (self.homePath)
|
||||
# Test if we can write (use /tmp for root path since /public_html doesn't exist at /)
|
||||
test_path = '/tmp' if self.homePath == '/' else (self.homePath + '/public_html')
|
||||
command = 'touch %s/hello.txt' % (test_path)
|
||||
result = ProcessUtilities.outputExecutioner(command)
|
||||
if result is None:
|
||||
result = ''
|
||||
|
||||
if result.find('cannot touch') > -1:
|
||||
if isinstance(result, (str, bytes)) and ('cannot touch' in str(result) or 'Permission denied' in str(result)):
|
||||
RemoveOK = 0
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Directory {self.homePath} is not writable, removing chattr flags")
|
||||
logging.writeToFile(f"Directory {self.homePath} is not writable, removing chattr flags")
|
||||
|
||||
command = 'chattr -R -i %s' % (self.homePath)
|
||||
result = ProcessUtilities.executioner(command)
|
||||
if result.find('cannot') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to remove chattr -i from {self.homePath}: {result}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Warning: Failed to remove chattr -i from {self.homePath}: {result}")
|
||||
|
||||
else:
|
||||
command = 'rm -f %s/public_html/hello.txt' % (self.homePath)
|
||||
command = 'rm -f %s/hello.txt' % (test_path)
|
||||
ProcessUtilities.executioner(command)
|
||||
|
||||
for item in self.data['fileAndFolders']:
|
||||
itemPath = self.data['path'] + '/' + item
|
||||
base = self.data['path'].rstrip('/') or '/'
|
||||
itemPath = base + '/' + item
|
||||
|
||||
# Security check for system files
|
||||
if itemPath.find('..') > -1 or itemPath.find(self.homePath) == -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Security violation: Attempted to delete outside allowed path: {itemPath}")
|
||||
if not ACLManager.isPathInsideHome(itemPath, self.homePath):
|
||||
logging.writeToFile(f"Security violation: Attempted to delete outside allowed path: {itemPath}")
|
||||
return self.ajaxPre(0, 'Not allowed to delete files outside allowed path!')
|
||||
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Deleting system file: {itemPath}")
|
||||
logging.writeToFile(f"Deleting system file: {itemPath}")
|
||||
|
||||
if skipTrash:
|
||||
command = 'rm -rf ' + self.returnPathEnclosed(itemPath)
|
||||
result = ProcessUtilities.executioner(command)
|
||||
if result.find('cannot') > -1 or result.find('Permission denied') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Failed to delete system file {itemPath}: {result}")
|
||||
return self.ajaxPre(0, f'Failed to delete {item}: {result}')
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully deleted system file: {itemPath}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Failed to delete system file {itemPath}: result={result}, trying Python fallback")
|
||||
if not self._deleteViaPythonBase64(itemPath, None):
|
||||
return self.ajaxPre(0, f'Failed to delete {item}')
|
||||
logging.writeToFile(f"Successfully deleted system file: {itemPath}")
|
||||
|
||||
|
||||
if RemoveOK == 0:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Restoring chattr +i flags for system path: {self.homePath}")
|
||||
logging.writeToFile(f"Restoring chattr +i flags for system path: {self.homePath}")
|
||||
command = 'chattr -R +i %s' % (self.homePath)
|
||||
result = ProcessUtilities.executioner(command)
|
||||
if result.find('cannot') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to restore chattr +i to system path {self.homePath}: {result}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Warning: Failed to restore chattr +i to system path {self.homePath}: result={result}")
|
||||
else:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully restored chattr +i to system path {self.homePath}")
|
||||
logging.writeToFile(f"Successfully restored chattr +i to system path {self.homePath}")
|
||||
|
||||
# Allow specific directories to remain mutable for system files
|
||||
mutable_dirs = ['/logs/', '/.trash/', '/backup/', '/incbackup/', '/lscache/', '/.cagefs/']
|
||||
@@ -563,17 +621,17 @@ class FileManager:
|
||||
dir_path = self.homePath + dir_name
|
||||
command = 'chattr -R -i %s' % (dir_path)
|
||||
result = ProcessUtilities.executioner(command)
|
||||
if result.find('cannot') > -1:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to remove chattr +i from system {dir_path}: {result}")
|
||||
if result != 1:
|
||||
logging.writeToFile(f"Warning: Failed to remove chattr +i from system {dir_path}: result={result}")
|
||||
else:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully removed chattr +i from system {dir_path}")
|
||||
logging.writeToFile(f"Successfully removed chattr +i from system {dir_path}")
|
||||
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"File deletion completed successfully for domain: {domainName}")
|
||||
logging.writeToFile(f"File deletion completed successfully for domain: {domainName}")
|
||||
json_data = json.dumps(finalData)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except BaseException as msg:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Critical error in deleteFolderOrFile: {str(msg)}")
|
||||
logging.writeToFile(f"Critical error in deleteFolderOrFile: {str(msg)}")
|
||||
return self.ajaxPre(0, f"File deletion failed: {str(msg)}")
|
||||
|
||||
def restore(self):
|
||||
@@ -593,8 +651,7 @@ class FileManager:
|
||||
|
||||
for item in self.data['fileAndFolders']:
|
||||
|
||||
if (self.data['path'] + '/' + item).find('..') > -1 or (self.data['path'] + '/' + item).find(
|
||||
self.homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['path'] + '/' + item, self.homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
trashPath = '%s/.trash' % (self.homePath)
|
||||
@@ -628,13 +685,12 @@ class FileManager:
|
||||
|
||||
homePath = '/home/%s' % (domainName)
|
||||
|
||||
if self.data['newPath'].find('..') > -1 or self.data['newPath'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['newPath'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if len(self.data['fileAndFolders']) == 1:
|
||||
|
||||
if (self.data['basePath'] + '/' + self.data['fileAndFolders'][0]).find('..') > -1 or (
|
||||
self.data['basePath'] + '/' + self.data['fileAndFolders'][0]).find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + self.data['fileAndFolders'][0], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = 'yes| cp -Rf %s %s' % (
|
||||
@@ -654,8 +710,7 @@ class FileManager:
|
||||
ProcessUtilities.executioner(command, website.externalApp)
|
||||
|
||||
for item in self.data['fileAndFolders']:
|
||||
if (self.data['basePath'] + '/' + item).find('..') > -1 or (self.data['basePath'] + '/' + item).find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + item, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = '%scp -Rf ' % ('yes |') + self.returnPathEnclosed(
|
||||
@@ -672,13 +727,12 @@ class FileManager:
|
||||
|
||||
homePath = '/'
|
||||
|
||||
if self.data['newPath'].find('..') > -1 or self.data['newPath'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['newPath'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if len(self.data['fileAndFolders']) == 1:
|
||||
|
||||
if (self.data['basePath'] + '/' + self.data['fileAndFolders'][0]).find('..') > -1 or (
|
||||
self.data['basePath'] + '/' + self.data['fileAndFolders'][0]).find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + self.data['fileAndFolders'][0], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = 'yes| cp -Rf %s %s' % (
|
||||
@@ -698,9 +752,7 @@ class FileManager:
|
||||
ProcessUtilities.executioner(command)
|
||||
|
||||
for item in self.data['fileAndFolders']:
|
||||
if (self.data['basePath'] + '/' + item).find('..') > -1 or (
|
||||
self.data['basePath'] + '/' + item).find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + item, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = '%scp -Rf ' % ('yes |') + self.returnPathEnclosed(
|
||||
@@ -735,12 +787,10 @@ class FileManager:
|
||||
|
||||
for item in self.data['fileAndFolders']:
|
||||
|
||||
if (self.data['basePath'] + '/' + item).find('..') > -1 or (self.data['basePath'] + '/' + item).find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + item, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if (self.data['newPath'] + '/' + item).find('..') > -1 or (self.data['newPath'] + '/' + item).find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['newPath'] + '/' + item, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = 'mv ' + self.returnPathEnclosed(
|
||||
@@ -765,13 +815,10 @@ class FileManager:
|
||||
|
||||
for item in self.data['fileAndFolders']:
|
||||
|
||||
if (self.data['basePath'] + '/' + item).find('..') > -1 or (
|
||||
self.data['basePath'] + '/' + item).find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + item, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if (self.data['newPath'] + '/' + item).find('..') > -1 or (self.data['newPath'] + '/' + item).find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['newPath'] + '/' + item, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = 'mv ' + self.returnPathEnclosed(
|
||||
@@ -803,11 +850,10 @@ class FileManager:
|
||||
|
||||
homePath = '/home/%s' % (domainName)
|
||||
|
||||
if (self.data['basePath'] + '/' + self.data['existingName']).find('..') > -1 or (
|
||||
self.data['basePath'] + '/' + self.data['existingName']).find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + self.data['existingName'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if (self.data['newFileName']).find('..') > -1 or (self.data['basePath']).find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + self.data['newFileName'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = 'mv ' + self.returnPathEnclosed(
|
||||
@@ -819,11 +865,10 @@ class FileManager:
|
||||
except:
|
||||
homePath = '/'
|
||||
|
||||
if (self.data['basePath'] + '/' + self.data['existingName']).find('..') > -1 or (
|
||||
self.data['basePath'] + '/' + self.data['existingName']).find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + self.data['existingName'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if (self.data['newFileName']).find('..') > -1 or (self.data['basePath']).find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + '/' + self.data['newFileName'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = 'mv ' + self.returnPathEnclosed(
|
||||
@@ -850,7 +895,7 @@ class FileManager:
|
||||
|
||||
pathCheck = '/home/%s' % (domainName)
|
||||
|
||||
if self.data['fileName'].find(pathCheck) == -1 or self.data['fileName'].find('..') > -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['fileName'], pathCheck):
|
||||
return self.ajaxPre(0, 'Not allowed.')
|
||||
|
||||
# Ensure proper UTF-8 handling for file reading
|
||||
@@ -860,7 +905,7 @@ class FileManager:
|
||||
except:
|
||||
pathCheck = '/'
|
||||
|
||||
if self.data['fileName'].find(pathCheck) == -1 or self.data['fileName'].find('..') > -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['fileName'], pathCheck):
|
||||
return self.ajaxPre(0, 'Not allowed.')
|
||||
|
||||
# Ensure proper UTF-8 handling for file reading
|
||||
@@ -958,11 +1003,11 @@ class FileManager:
|
||||
if result.find('->') > -1:
|
||||
return self.ajaxPre(0, "Symlink attack.")
|
||||
|
||||
if ACLManager.commandInjectionCheck(self.data['completePath'] + '/' + myfile.name) == 1:
|
||||
uploadPathFull = self.data['completePath'] + '/' + myfile.name
|
||||
if not ACLManager.isFilePathSafeForShell(uploadPathFull):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if (self.data['completePath'] + '/' + myfile.name).find(pathCheck) == -1 or (
|
||||
(self.data['completePath'] + '/' + myfile.name)).find('..') > -1:
|
||||
if not ACLManager.isPathInsideHome(uploadPathFull, pathCheck):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = 'cp ' + self.returnPathEnclosed(
|
||||
@@ -985,11 +1030,11 @@ class FileManager:
|
||||
command = 'ls -la %s' % (self.data['completePath'])
|
||||
result = ProcessUtilities.outputExecutioner(command)
|
||||
logging.writeToFile("upload file res %s" % result)
|
||||
if ACLManager.commandInjectionCheck(self.data['completePath'] + '/' + myfile.name) == 1:
|
||||
uploadPathFull = self.data['completePath'] + '/' + myfile.name
|
||||
if not ACLManager.isFilePathSafeForShell(uploadPathFull):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if (self.data['completePath'] + '/' + myfile.name).find(pathCheck) == -1 or (
|
||||
(self.data['completePath'] + '/' + myfile.name)).find('..') > -1:
|
||||
if not ACLManager.isPathInsideHome(uploadPathFull, pathCheck):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = 'cp ' + self.returnPathEnclosed(
|
||||
@@ -1034,10 +1079,10 @@ class FileManager:
|
||||
|
||||
homePath = '/home/%s' % (domainName)
|
||||
|
||||
if self.data['extractionLocation'].find('..') > -1 or self.data['extractionLocation'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['extractionLocation'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if self.data['fileToExtract'].find('..') > -1 or self.data['fileToExtract'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['fileToExtract'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if self.data['extractionType'] == 'zip':
|
||||
@@ -1065,11 +1110,10 @@ class FileManager:
|
||||
|
||||
homePath = '/'
|
||||
|
||||
if self.data['extractionLocation'].find('..') > -1 or self.data['extractionLocation'].find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['extractionLocation'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if self.data['fileToExtract'].find('..') > -1 or self.data['fileToExtract'].find(homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['fileToExtract'], homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
if self.data['extractionType'] == 'zip':
|
||||
@@ -1130,8 +1174,7 @@ class FileManager:
|
||||
|
||||
for item in self.data['listOfFiles']:
|
||||
|
||||
if (self.data['basePath'] + item).find('..') > -1 or (self.data['basePath'] + item).find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + item, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
|
||||
command = '%s%s ' % (command, self.returnPathEnclosed(item))
|
||||
@@ -1168,8 +1211,7 @@ class FileManager:
|
||||
|
||||
for item in self.data['listOfFiles']:
|
||||
|
||||
if (self.data['basePath'] + item).find('..') > -1 or (self.data['basePath'] + item).find(
|
||||
homePath) == -1:
|
||||
if not ACLManager.isPathInsideHome(self.data['basePath'] + item, homePath):
|
||||
return self.ajaxPre(0, 'Not allowed to move in this path, please choose location inside home!')
|
||||
command = '%s%s ' % (command, self.returnPathEnclosed(item))
|
||||
|
||||
|
||||
@@ -82,6 +82,15 @@ fileManager.controller('fileManagerCtrl', function ($scope, $http, FileUploader,
|
||||
$scope.showUploadBox = function () {
|
||||
$('#uploadBox').modal('show');
|
||||
};
|
||||
// Fix aria-hidden a11y: move focus out of modal before hide so no focused descendant retains focus
|
||||
$(document).on('hide.bs.modal', '.modal', function () {
|
||||
var modal = this;
|
||||
if (document.activeElement && modal.contains(document.activeElement)) {
|
||||
var trigger = document.getElementById('uploadTriggerBtn');
|
||||
if (trigger && modal.id === 'uploadBox') { trigger.focus(); }
|
||||
else { document.activeElement.blur(); }
|
||||
}
|
||||
});
|
||||
|
||||
$scope.showHTMLEditorModal = function (MainFM= 0) {
|
||||
$scope.htmlEditorLoading = false;
|
||||
@@ -1147,7 +1156,8 @@ fileManager.controller('fileManagerCtrl', function ($scope, $http, FileUploader,
|
||||
});
|
||||
$scope.fetchForTableSecondary(null, 'refresh');
|
||||
} else {
|
||||
var notification = alertify.notify('Files/Folders can not be deleted', 'error', 5, function () {
|
||||
var errMsg = (response.data && response.data.error_message) ? response.data.error_message : 'Files/Folders can not be deleted';
|
||||
var notification = alertify.notify(errMsg, 'error', 8, function () {
|
||||
console.log('dismissed');
|
||||
});
|
||||
}
|
||||
@@ -1155,6 +1165,10 @@ fileManager.controller('fileManagerCtrl', function ($scope, $http, FileUploader,
|
||||
}
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
var err = (response && response.data && (response.data.error_message || response.data.message)) ||
|
||||
(response && response.statusText) || 'Request failed';
|
||||
if (response && response.status === 0) err = 'Network error';
|
||||
alertify.notify(err, 'error', 8);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -156,6 +156,14 @@ function findFileExtension(fileName) {
|
||||
$scope.showUploadBox = function () {
|
||||
$("#uploadBox").modal();
|
||||
};
|
||||
$(document).on("hide.bs.modal", ".modal", function () {
|
||||
var modal = this;
|
||||
if (document.activeElement && modal.contains(document.activeElement)) {
|
||||
var trigger = document.getElementById("uploadTriggerBtn");
|
||||
if (trigger && modal.id === "uploadBox") { trigger.focus(); }
|
||||
else { document.activeElement.blur(); }
|
||||
}
|
||||
});
|
||||
|
||||
$scope.showHTMLEditorModal = function (MainFM = 0) {
|
||||
$scope.fileInEditor = allFilesAndFolders[0];
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<link rel="icon" type="image/png" href="{% static 'baseTemplate/assets/finalBase/favicon.png' %}">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
|
||||
integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{% static 'filemanager/images/fonts/css/font-awesome.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'filemanager/css/fileManager.css' %}">
|
||||
@@ -186,7 +186,7 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"
|
||||
integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/js/bootstrap.min.js"
|
||||
integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<link rel="icon" type="image/png" href="{% static 'baseTemplate/assets/finalBase/favicon.png' %}">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{% static 'filemanager/images/fonts/css/font-awesome.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'filemanager/css/fileManager.css' %}">
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</div-->
|
||||
<ul class="nav mr-10">
|
||||
<li class="nav-item">
|
||||
<a onclick="return false;" ng-click="showUploadBox()" class="nav-link point-events" href="#"><i class="fa fa-upload" aria-hidden="true"></i> {% trans "Upload" %}</a>
|
||||
<a id="uploadTriggerBtn" onclick="return false;" ng-click="showUploadBox()" class="nav-link point-events" href="#"><i class="fa fa-upload" aria-hidden="true"></i> {% trans "Upload" %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a onclick="return false;" ng-click="showCreateFileModal()" class="nav-link point-events" href="#"><i class="fa fa-plus-square" aria-hidden="true"></i> {% trans "New File" %}</a>
|
||||
@@ -709,7 +709,7 @@
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
<!-- HTML Editor Include -->
|
||||
|
||||
@@ -439,7 +439,7 @@
|
||||
<div class="fm-toolbar">
|
||||
<ul class="nav">
|
||||
<li class="nav-item">
|
||||
<a onclick="return false;" ng-click="showUploadBox()" class="nav-link point-events" href="#">
|
||||
<a id="uploadTriggerBtn" onclick="return false;" ng-click="showUploadBox()" class="nav-link point-events" href="#">
|
||||
<i class="fa fa-upload" aria-hidden="true"></i> {% trans "Upload" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1001,7 +1001,7 @@
|
||||
</div>
|
||||
|
||||
<div class="fm-toolbar-group">
|
||||
<button class="fm-btn primary" ng-click="showUploadBox()">
|
||||
<button id="uploadTriggerBtn" class="fm-btn primary" ng-click="showUploadBox()">
|
||||
<i class="fas fa-cloud-upload-alt"></i>
|
||||
{% trans "Upload" %}
|
||||
</button>
|
||||
|
||||
@@ -210,11 +210,18 @@ def upload(request):
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if ACLManager.checkOwnership(data['domainName'], admin, currentACL) == 1:
|
||||
domainName = data.get('domainName', '')
|
||||
if domainName == '':
|
||||
# Root File Manager: allow only admin
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadErrorJson()
|
||||
elif ACLManager.checkOwnership(domainName, admin, currentACL) == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadErrorJson()
|
||||
except:
|
||||
except Exception:
|
||||
return ACLManager.loadErrorJson()
|
||||
|
||||
fm = FM(request, data)
|
||||
|
||||
28
install/safe-delete-path
Normal file
28
install/safe-delete-path
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Helper script for CyberPanel File Manager: delete a file/dir by base64-encoded path.
|
||||
Invoked by lscpd as root. Exits 0 on success, 1 on failure."""
|
||||
import sys
|
||||
import os
|
||||
import base64
|
||||
import shutil
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit(1)
|
||||
try:
|
||||
p_b64 = sys.argv[1]
|
||||
path = base64.b64decode(p_b64).decode('utf-8')
|
||||
if not path or not os.path.isabs(path):
|
||||
sys.exit(1)
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
elif os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
24
install/safe-move-path
Normal file
24
install/safe-move-path
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Helper script for CyberPanel File Manager: move src to dest by base64-encoded paths.
|
||||
Invoked by lscpd as root. Exits 0 on success, 1 on failure."""
|
||||
import sys
|
||||
import os
|
||||
import base64
|
||||
import shutil
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
sys.exit(1)
|
||||
try:
|
||||
s_b64, d_b64 = sys.argv[1], sys.argv[2]
|
||||
src = base64.b64decode(s_b64).decode('utf-8')
|
||||
dest = base64.b64decode(d_b64).decode('utf-8')
|
||||
if not src or not dest or not os.path.isabs(src):
|
||||
sys.exit(1)
|
||||
shutil.move(src, dest)
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -154,6 +154,41 @@ class ACLManager:
|
||||
except BaseException as msg:
|
||||
logging.writeToFile('%s. [32:commandInjectionCheck]' % (str(msg)))
|
||||
|
||||
@staticmethod
|
||||
def isPathInsideHome(path, home_path):
|
||||
"""
|
||||
Check if path is inside the allowed home directory. Uses normpath to correctly
|
||||
allow filenames like 'file..name.txt' while rejecting path traversal (e.g. ../../etc).
|
||||
"""
|
||||
try:
|
||||
if not path or not isinstance(path, str):
|
||||
return False
|
||||
path = os.path.normpath(path)
|
||||
if not os.path.isabs(path):
|
||||
return False
|
||||
base = os.path.realpath(home_path)
|
||||
if base == '/':
|
||||
return True
|
||||
return path == base or path.startswith(base + os.sep)
|
||||
except (OSError, TypeError) as msg:
|
||||
logging.writeToFile('%s. [isPathInsideHome]' % (str(msg)))
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def isFilePathSafeForShell(path):
|
||||
"""
|
||||
Check if path is safe for shell when passed in single quotes. Only blocks
|
||||
characters that break single-quoted strings: quote, null, newline.
|
||||
Allows ( ) : & [ ] etc. since they are harmless inside single quotes.
|
||||
"""
|
||||
try:
|
||||
if not path or not isinstance(path, str):
|
||||
return False
|
||||
return "'" not in path and '\0' not in path and '\n' not in path
|
||||
except (TypeError, AttributeError) as msg:
|
||||
logging.writeToFile('%s. [isFilePathSafeForShell]' % (str(msg)))
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def loadedACL(val):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user