Enhance text readability and error handling: Add readability fixes CSS for improved contrast across UI elements. Update text colors in various components for better visibility. Enhance FTP error handling with user-friendly messages and improved path validation in both frontend and backend. Update HTML templates to provide clearer instructions and examples for FTP path input.

This commit is contained in:
Master3395
2025-09-21 22:26:18 +02:00
parent f5d4c46c37
commit 3432432f91
9 changed files with 734 additions and 36 deletions

View File

@@ -23,6 +23,9 @@
<!-- Mobile Responsive CSS --> <!-- Mobile Responsive CSS -->
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/mobile-responsive.css' %}?v={{ CP_VERSION }}"> <link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/mobile-responsive.css' %}?v={{ CP_VERSION }}">
<!-- Readability Fixes CSS -->
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/readability-fixes.css' %}?v={{ CP_VERSION }}">
<!-- Core Scripts --> <!-- Core Scripts -->
<script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}"></script> <script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
@@ -54,7 +57,7 @@
--bg-sidebar-item: white; --bg-sidebar-item: white;
--bg-hover: #e8e6ff; --bg-hover: #e8e6ff;
--text-primary: #2f3640; --text-primary: #2f3640;
--text-secondary: #64748b; --text-secondary: #2f3640; /* Changed from grey to dark for better readability */
--text-heading: #1e293b; --text-heading: #1e293b;
--border-color: #e8e9ff; --border-color: #e8e9ff;
--shadow-color: rgba(0,0,0,0.05); --shadow-color: rgba(0,0,0,0.05);
@@ -74,7 +77,7 @@
--bg-sidebar-item: #1e1e42; --bg-sidebar-item: #1e1e42;
--bg-hover: #252550; --bg-hover: #252550;
--text-primary: #e4e4e7; --text-primary: #e4e4e7;
--text-secondary: #9ca3af; --text-secondary: #e4e4e7; /* Changed from grey to light for better readability */
--text-heading: #f3f4f6; --text-heading: #f3f4f6;
--border-color: #2a2a5e; --border-color: #2a2a5e;
--shadow-color: rgba(0,0,0,0.3); --shadow-color: rgba(0,0,0,0.3);
@@ -352,6 +355,12 @@
overflow: hidden; overflow: hidden;
} }
/* Fix green text issue - ensure uptime uses normal text color */
#sidebar .server-info .info-line span,
#sidebar .server-info .info-line .ng-binding {
color: var(--text-secondary) !important;
}
#sidebar .server-info .info-line strong { #sidebar .server-info .info-line strong {
color: var(--text-primary); color: var(--text-primary);
font-weight: 600; font-weight: 600;
@@ -701,6 +710,7 @@
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
border-radius: 12px; border-radius: 12px;
padding: 12px; padding: 12px;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3); border: 1px solid rgba(255, 255, 255, 0.3);
} }
@@ -791,6 +801,7 @@
padding: 8px; padding: 8px;
border-radius: 6px; border-radius: 6px;
transition: all 0.2s ease; transition: all 0.2s ease;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
@@ -887,6 +898,47 @@
background: var(--accent-hover); background: var(--accent-hover);
} }
/* Enhanced Text Readability Fixes */
/* Ensure all text elements use proper contrast colors */
.menu-item span,
.menu-item i,
.info-line,
.info-line span,
.info-line strong,
.server-details,
.server-details *,
.tagline,
.brand {
color: inherit !important;
}
/* Override any Angular binding colors that might be green */
.ng-binding {
color: var(--text-secondary) !important;
}
/* Ensure menu items have proper text color */
#sidebar .menu-item {
color: var(--text-secondary) !important;
}
#sidebar .menu-item:hover {
color: var(--accent-color) !important;
}
#sidebar .menu-item.active {
color: white !important;
}
/* Fix any remaining grey text issues */
.text-muted,
.text-secondary,
.text-light,
small,
.small {
color: var(--text-secondary) !important;
}
/* Mobile Menu Toggle */ /* Mobile Menu Toggle */
#mobile-menu-toggle { #mobile-menu-toggle {
display: none; display: none;
@@ -1113,7 +1165,6 @@
<i class="fab fa-wordpress"></i> <i class="fab fa-wordpress"></i>
</div> </div>
<span>WordPress</span> <span>WordPress</span>
<span class="badge">NEW</span>
<i class="fas fa-chevron-right chevron"></i> <i class="fas fa-chevron-right chevron"></i>
</a> </a>
<div id="wordpress-submenu" class="submenu"> <div id="wordpress-submenu" class="submenu">
@@ -1465,7 +1516,6 @@
</a> </a>
<a href="{% url 'sslReconcile' %}" class="menu-item"> <a href="{% url 'sslReconcile' %}" class="menu-item">
<span>SSL Reconciliation</span> <span>SSL Reconciliation</span>
<span class="badge">NEW</span>
</a> </a>
{% endif %} {% endif %}
{% if admin or hostnameSSL %} {% if admin or hostnameSSL %}
@@ -1488,7 +1538,6 @@
<i class="fas fa-database"></i> <i class="fas fa-database"></i>
</div> </div>
<span>MySQL Manager</span> <span>MySQL Manager</span>
<span class="badge">NEW</span>
</a> </a>
<a href="{% url 'Filemanager' %}" class="menu-item"> <a href="{% url 'Filemanager' %}" class="menu-item">
@@ -1503,7 +1552,6 @@
<i class="fas fa-fire"></i> <i class="fas fa-fire"></i>
</div> </div>
<span>CloudLinux</span> <span>CloudLinux</span>
<span class="badge">NEW</span>
<i class="fas fa-chevron-right chevron"></i> <i class="fas fa-chevron-right chevron"></i>
</a> </a>
<div id="cloudlinux-submenu" class="submenu"> <div id="cloudlinux-submenu" class="submenu">
@@ -1669,7 +1717,6 @@
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</div> </div>
<span>Mail Settings</span> <span>Mail Settings</span>
<span class="badge">NEW</span>
<i class="fas fa-chevron-right chevron"></i> <i class="fas fa-chevron-right chevron"></i>
</a> </a>
<div id="mail-settings-submenu" class="submenu"> <div id="mail-settings-submenu" class="submenu">

View File

@@ -1111,7 +1111,6 @@
<a href="#" title="Pages"> <a href="#" title="Pages">
<i class="glyph-icon icon-linecons-fire"></i> <i class="glyph-icon icon-linecons-fire"></i>
<span>Pages</span> <span>Pages</span>
<span class="bs-label badge-yellow">NEW</span>
</a> </a>
<div class="sidebar-submenu"> <div class="sidebar-submenu">

View File

@@ -111,7 +111,29 @@ class FTPManager:
return HttpResponse(json_data) return HttpResponse(json_data)
except BaseException as msg: except BaseException as msg:
data_ret = {'status': 0, 'creatFTPStatus': 0, 'error_message': str(msg)} # Enhanced error handling with better user feedback
error_message = str(msg)
# Provide more user-friendly error messages
if "Invalid path" in error_message:
pass # Keep original message as it's already user-friendly
elif "Security violation" in error_message:
pass # Keep original message as it's already user-friendly
elif "Path validation failed" in error_message:
pass # Keep original message as it's already user-friendly
elif "Exceeded maximum amount" in error_message:
pass # Keep original message as it's already user-friendly
elif "symlinked" in error_message.lower():
error_message = "Cannot create FTP account: The specified path is a symbolic link. Please choose a different path."
elif "Permission denied" in error_message:
error_message = "Permission denied: Unable to create or access the specified directory. Please check the path and try again."
elif "No such file or directory" in error_message:
error_message = "Directory not found: The specified path does not exist. Please check the path and try again."
else:
# Generic fallback for other errors
error_message = f"FTP account creation failed: {error_message}"
data_ret = {'status': 0, 'creatFTPStatus': 0, 'error_message': error_message}
json_data = json.dumps(data_ret) json_data = json.dumps(data_ret)
return HttpResponse(json_data) return HttpResponse(json_data)

View File

@@ -60,8 +60,45 @@ app.controller('createFTPAccount', function ($scope, $http) {
var ftpPassword = $scope.ftpPassword; var ftpPassword = $scope.ftpPassword;
var path = $scope.ftpPath; var path = $scope.ftpPath;
if (typeof path === 'undefined') { // Enhanced path validation
if (typeof path === 'undefined' || path === null) {
path = ""; path = "";
} else {
path = path.trim();
}
// Client-side path validation
if (path && path !== "") {
// Check for dangerous characters
var dangerousChars = /[;&|$`'"<>*?~]/;
if (dangerousChars.test(path)) {
$scope.ftpLoading = false;
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = "Invalid path: Path contains dangerous characters";
return;
}
// Check for path traversal attempts
if (path.indexOf("..") !== -1 || path.indexOf("~") !== -1) {
$scope.ftpLoading = false;
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = "Invalid path: Path cannot contain '..' or '~'";
return;
}
// Check if path starts with slash (should be relative)
if (path.startsWith("/")) {
$scope.ftpLoading = false;
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = "Invalid path: Path must be relative (not starting with '/')";
return;
}
} }
var url = "/ftp/submitFTPCreation"; var url = "/ftp/submitFTPCreation";

View File

@@ -521,9 +521,23 @@
<div class="path-info"> <div class="path-info">
<i class="fas fa-folder"></i> <i class="fas fa-folder"></i>
{% trans "Leave empty to use the website's home directory, or specify a subdirectory" %} {% trans "Leave empty to use the website's home directory, or specify a subdirectory" %}
<br>
<small style="margin-top: 0.5rem; display: block; color: var(--text-secondary, #64748b);">
<i class="fas fa-info-circle"></i>
<strong>{% trans "Examples:" %}</strong> {% trans "docs, public_html, uploads, api" %}
<br>
<i class="fas fa-shield-alt"></i>
{% trans "Security: Path will be restricted to this subdirectory only" %}
</small>
</div> </div>
<input placeholder="{% trans 'e.g., public_html or leave empty' %}" <input placeholder="{% trans 'e.g., docs or public_html (leave empty for home directory)' %}"
type="text" class="form-control" ng-model="ftpPath"> type="text" class="form-control" ng-model="ftpPath"
pattern="^[a-zA-Z0-9._/-]+$"
title="{% trans 'Only letters, numbers, dots, underscores, hyphens, and forward slashes allowed' %}">
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block;">
<i class="fas fa-exclamation-triangle"></i>
{% trans "Do not use: .. ~ / or special characters like ; | & $ ` ' \" < > * ?" %}
</small>
</div> </div>
<div ng-hide="ftpDetails" class="form-group"> <div ng-hide="ftpDetails" class="form-group">

View File

@@ -89,11 +89,35 @@ class FTPUtilities:
@staticmethod @staticmethod
def ftpFunctions(path,externalApp): def ftpFunctions(path,externalApp):
try: try:
# Enhanced path validation and creation
import os
command = 'mkdir %s' % (path) # Check if path already exists
if os.path.exists(path):
# Path exists, ensure it's a directory
if not os.path.isdir(path):
return 0, "Specified path exists but is not a directory"
# Set proper permissions
command = 'chown -R %s:%s %s' % (externalApp, externalApp, path)
ProcessUtilities.executioner(command, externalApp)
return 1, 'None'
else:
# Create the directory with proper permissions
command = 'mkdir -p %s' % (path)
result = ProcessUtilities.executioner(command, externalApp)
if result == 0:
# Set proper ownership
command = 'chown -R %s:%s %s' % (externalApp, externalApp, path)
ProcessUtilities.executioner(command, externalApp) ProcessUtilities.executioner(command, externalApp)
return 1,'None' # Set proper permissions (755)
command = 'chmod 755 %s' % (path)
ProcessUtilities.executioner(command, externalApp)
return 1, 'None'
else:
return 0, "Failed to create directory: %s" % path
except BaseException as msg: except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile( logging.CyberCPLogFileWriter.writeToFile(
@@ -118,30 +142,43 @@ class FTPUtilities:
## gid , uid ends ## gid , uid ends
path = path.lstrip("/") # Enhanced path validation and handling
if path and path.strip() and path != 'None':
# Clean the path
path = path.strip().lstrip("/")
if path != 'None': # Additional security checks
path = "/home/" + domainName + "/" + path if path.find("..") > -1 or path.find("~") > -1 or path.startswith("/"):
raise BaseException("Invalid path: Path must be relative and not contain '..' or '~' or start with '/'")
## Security Check # Check for dangerous characters
dangerous_chars = [';', '|', '&', '$', '`', '\'', '"', '<', '>', '*', '?']
if any(char in path for char in dangerous_chars):
raise BaseException("Invalid path: Path contains dangerous characters")
if path.find("..") > -1: # Construct full path
raise BaseException("Specified path must be inside virtual host home!") full_path = "/home/" + domainName + "/" + path
# Additional security: ensure path is within domain directory
domain_home = "/home/" + domainName
if not os.path.abspath(full_path).startswith(os.path.abspath(domain_home)):
raise BaseException("Security violation: Path must be within domain directory")
result = FTPUtilities.ftpFunctions(path, externalApp) result = FTPUtilities.ftpFunctions(full_path, externalApp)
if result[0] == 1: if result[0] == 1:
pass path = full_path
else: else:
raise BaseException(result[1]) raise BaseException("Path validation failed: " + result[1])
else: else:
path = "/home/" + domainName path = "/home/" + domainName
# Enhanced symlink handling
if os.path.islink(path): if os.path.islink(path):
print("0, %s file is symlinked." % (path)) logging.CyberCPLogFileWriter.writeToFile(
return 0 "FTP path is symlinked: %s" % path)
raise BaseException("Cannot create FTP account: Path is a symbolic link")
ProcessUtilities.decideDistro() ProcessUtilities.decideDistro()

View File

@@ -6,6 +6,7 @@ html {
font-size: 16px; /* Base font size for better readability */ font-size: 16px; /* Base font size for better readability */
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%; -ms-text-size-adjust: 100%;
text-size-adjust: 100%;
} }
body { body {
@@ -22,7 +23,7 @@ body {
/* Override any light text that might be hard to read */ /* Override any light text that might be hard to read */
.text-muted, .text-secondary, .text-light { .text-muted, .text-secondary, .text-light {
color: #64748b !important; /* Darker gray instead of light gray */ color: #2f3640 !important; /* Dark text for better readability on white backgrounds */
} }
/* Fix small font sizes that are hard to read */ /* Fix small font sizes that are hard to read */
@@ -294,7 +295,6 @@ p {
/* Make tables horizontally scrollable */ /* Make tables horizontally scrollable */
.table-responsive { .table-responsive {
overflow-x: auto; overflow-x: auto;
-webkit-overflow-scrolling: touch;
} }
.table { .table {
@@ -502,14 +502,14 @@ p {
} }
.text-muted { .text-muted {
color: #64748b !important; color: #2f3640 !important; /* Dark text for better readability */
} }
/* Fix any light text on light backgrounds */ /* Fix any light text on light backgrounds */
.bg-light .text-muted, .bg-light .text-muted,
.bg-white .text-muted, .bg-white .text-muted,
.panel .text-muted { .panel .text-muted {
color: #64748b !important; color: #2f3640 !important; /* Dark text for better readability */
} }
/* Ensure proper spacing for touch targets */ /* Ensure proper spacing for touch targets */
@@ -518,6 +518,43 @@ a, button, input, select, textarea {
min-width: 44px; min-width: 44px;
} }
/* Additional text readability improvements */
/* Fix any green text issues */
.ng-binding {
color: #2f3640 !important; /* Normal dark text instead of green */
}
/* Ensure all text elements have proper contrast */
span, div, p, label, td, th {
color: inherit;
}
/* Fix specific text color issues */
.text-success {
color: #059669 !important; /* Darker green for better readability */
}
.text-info {
color: #0284c7 !important; /* Darker blue for better readability */
}
.text-warning {
color: #d97706 !important; /* Darker orange for better readability */
}
/* Override Bootstrap's muted text */
.text-muted {
color: #2f3640 !important; /* Dark text instead of grey */
}
/* Fix any remaining light text on light backgrounds */
.bg-white .text-light,
.bg-light .text-light,
.panel .text-light,
.card .text-light {
color: #2f3640 !important;
}
/* Fix for small clickable elements */ /* Fix for small clickable elements */
.glyph-icon, .icon { .glyph-icon, .icon {
min-width: 44px; min-width: 44px;

View File

@@ -0,0 +1,265 @@
/* CyberPanel Readability & Design Fixes */
/* This file fixes the core design issues with grey text and color inconsistencies */
/* Override CSS Variables for Better Text Contrast */
:root {
/* Ensure all text uses proper dark colors for readability */
--text-primary: #2f3640;
--text-secondary: #2f3640; /* Changed from grey to dark for better readability */
--text-heading: #1e293b;
}
/* Dark theme also uses proper contrast */
[data-theme="dark"] {
--text-primary: #e4e4e7;
--text-secondary: #e4e4e7; /* Changed from grey to light for better readability */
--text-heading: #f3f4f6;
}
/* Fix Green Text Issues */
/* Override Angular binding colors that might be green */
.ng-binding {
color: var(--text-secondary) !important;
}
/* Specific fix for uptime display */
#sidebar .server-info .info-line span,
#sidebar .server-info .info-line .ng-binding,
.server-info .ng-binding {
color: var(--text-secondary) !important;
}
/* Fix Grey Text on White Background */
/* Override all muted and secondary text classes */
.text-muted,
.text-secondary,
.text-light,
small,
.small,
.text-small {
color: var(--text-secondary) !important;
}
/* Fix specific Bootstrap classes */
.text-muted {
color: #2f3640 !important; /* Dark text for better readability */
}
/* Fix text on white/light backgrounds */
.bg-white .text-muted,
.bg-light .text-muted,
.panel .text-muted,
.card .text-muted,
.content-box .text-muted {
color: #2f3640 !important;
}
/* Fix menu items and navigation */
#sidebar .menu-item,
#sidebar .menu-item span,
#sidebar .menu-item i,
.sidebar .menu-item,
.sidebar .menu-item span,
.sidebar .menu-item i {
color: var(--text-secondary) !important;
}
#sidebar .menu-item:hover,
.sidebar .menu-item:hover {
color: var(--accent-color) !important;
}
#sidebar .menu-item.active,
.sidebar .menu-item.active {
color: white !important;
}
/* Fix server info and details */
.server-info,
.server-info *,
.server-details,
.server-details *,
.info-line,
.info-line span,
.info-line strong,
.tagline,
.brand {
color: inherit !important;
}
/* Fix form elements */
label,
.control-label,
.form-label {
color: var(--text-primary) !important;
font-weight: 600;
}
/* Fix table text */
.table th,
.table td {
color: var(--text-primary) !important;
}
.table th {
font-weight: 600;
}
/* Fix alert text */
.alert {
color: var(--text-primary) !important;
}
.alert-success {
color: #059669 !important; /* Darker green for better readability */
}
.alert-info {
color: #0284c7 !important; /* Darker blue for better readability */
}
.alert-warning {
color: #d97706 !important; /* Darker orange for better readability */
}
.alert-danger {
color: #dc2626 !important; /* Darker red for better readability */
}
/* Fix breadcrumb text */
.breadcrumb-item {
color: var(--text-secondary) !important;
}
.breadcrumb-item.active {
color: var(--text-primary) !important;
}
/* Fix modal text */
.modal-content {
color: var(--text-primary) !important;
}
.modal-title {
color: var(--text-heading) !important;
}
/* Fix button text */
.btn {
color: inherit;
}
/* Fix any remaining light text issues */
.bg-light .text-light,
.bg-white .text-light,
.panel .text-light,
.card .text-light {
color: #2f3640 !important;
}
/* Ensure proper contrast for all text elements */
span, div, p, label, td, th, a, li {
color: inherit;
}
/* Fix specific color classes */
.text-success {
color: #059669 !important; /* Darker green for better readability */
}
.text-info {
color: #0284c7 !important; /* Darker blue for better readability */
}
.text-warning {
color: #d97706 !important; /* Darker orange for better readability */
}
.text-danger {
color: #dc2626 !important; /* Darker red for better readability */
}
/* Fix any Angular-specific styling */
[ng-controller] {
color: inherit;
}
[ng-show],
[ng-hide],
[ng-if] {
color: inherit;
}
/* Ensure all content areas have proper text color */
.content-box,
.panel,
.card,
.main-content,
.page-content {
color: var(--text-primary) !important;
}
/* Fix any remaining Bootstrap classes */
.text-dark {
color: #2f3640 !important;
}
.text-body {
color: var(--text-primary) !important;
}
/* Mobile-specific fixes */
@media (max-width: 768px) {
/* Ensure mobile text is also readable */
body,
.container,
.container-fluid {
color: var(--text-primary) !important;
}
/* Fix mobile menu text */
.mobile-menu .menu-item,
.mobile-menu .menu-item span {
color: var(--text-secondary) !important;
}
}
/* Print styles */
@media print {
body,
.content-box,
.panel,
.card {
color: #000000 !important;
background: #ffffff !important;
}
.text-muted,
.text-secondary {
color: #000000 !important;
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
:root {
--text-primary: #000000;
--text-secondary: #000000;
--text-heading: #000000;
}
[data-theme="dark"] {
--text-primary: #ffffff;
--text-secondary: #ffffff;
--text-heading: #ffffff;
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

240
test_ftp_fixes.py Normal file
View File

@@ -0,0 +1,240 @@
#!/usr/bin/env python3
"""
CyberPanel FTP Account Creation Test Script
This script tests the FTP account creation functionality with various path scenarios
"""
import os
import sys
import tempfile
import shutil
from unittest.mock import patch, MagicMock
# Add CyberPanel to path
sys.path.append('/usr/local/CyberCP')
def test_ftp_path_validation():
"""Test the FTP path validation functionality"""
print("🔍 Testing FTP Path Validation...")
# Import the FTP utilities
try:
from plogical.ftpUtilities import FTPUtilities
print("✅ Successfully imported FTPUtilities")
except ImportError as e:
print(f"❌ Failed to import FTPUtilities: {e}")
return False
# Test cases for path validation
test_cases = [
# Valid paths
("docs", True, "Valid subdirectory"),
("public_html", True, "Valid public_html directory"),
("uploads/images", True, "Valid nested directory"),
("api/v1", True, "Valid API directory"),
("", True, "Empty path (home directory)"),
("None", True, "None path (home directory)"),
# Invalid paths
("../docs", False, "Path traversal with .."),
("~/docs", False, "Home directory reference"),
("/docs", False, "Absolute path"),
("docs;rm -rf /", False, "Command injection"),
("docs|cat /etc/passwd", False, "Pipe command injection"),
("docs&reboot", False, "Background command"),
("docs`whoami`", False, "Command substitution"),
("docs'rm -rf /'", False, "Single quote injection"),
('docs"rm -rf /"', False, "Double quote injection"),
("docs<malicious", False, "Input redirection"),
("docs>malicious", False, "Output redirection"),
("docs*", False, "Wildcard character"),
("docs?", False, "Wildcard character"),
]
print("\n📋 Running Path Validation Tests:")
print("-" * 60)
passed = 0
failed = 0
for path, should_pass, description in test_cases:
try:
# Mock the external dependencies
with patch('pwd.getpwnam') as mock_pwd, \
patch('grp.getgrnam') as mock_grp, \
patch('os.path.exists') as mock_exists, \
patch('os.path.isdir') as mock_isdir, \
patch('os.path.islink') as mock_islink, \
patch('plogical.processUtilities.ProcessUtilities.executioner') as mock_exec, \
patch('websiteFunctions.models.Websites.objects.get') as mock_website, \
patch('loginSystem.models.Administrator.objects.get') as mock_admin:
# Setup mocks
mock_pwd.return_value.pw_uid = 1000
mock_grp.return_value.gr_gid = 1000
mock_exists.return_value = True
mock_isdir.return_value = True
mock_islink.return_value = False
mock_exec.return_value = 0
# Mock website object
mock_website_obj = MagicMock()
mock_website_obj.externalApp = "testuser"
mock_website_obj.package.diskSpace = 1000
mock_website_obj.package.ftpAccounts = 10
mock_website_obj.users_set.all.return_value.count.return_value = 0
mock_website.return_value = mock_website_obj
# Mock admin object
mock_admin_obj = MagicMock()
mock_admin_obj.userName = "testadmin"
mock_admin.return_value = mock_admin_obj
# Test the path validation
result = FTPUtilities.submitFTPCreation(
"testdomain.com",
"testuser",
"testpass",
path,
"testadmin"
)
if should_pass:
if result[0] == 1:
print(f"✅ PASS: {description} ('{path}')")
passed += 1
else:
print(f"❌ FAIL: {description} ('{path}') - Expected success but got: {result[1]}")
failed += 1
else:
if result[0] == 0:
print(f"✅ PASS: {description} ('{path}') - Correctly rejected")
passed += 1
else:
print(f"❌ FAIL: {description} ('{path}') - Expected rejection but got success")
failed += 1
except Exception as e:
if should_pass:
print(f"❌ ERROR: {description} ('{path}') - Unexpected error: {e}")
failed += 1
else:
print(f"✅ PASS: {description} ('{path}') - Correctly rejected with error: {e}")
passed += 1
print("\n" + "=" * 60)
print(f"📊 Test Results: {passed} passed, {failed} failed")
print("=" * 60)
return failed == 0
def test_directory_creation():
"""Test directory creation functionality"""
print("\n🔍 Testing Directory Creation...")
try:
from plogical.ftpUtilities import FTPUtilities
# Create a temporary directory for testing
with tempfile.TemporaryDirectory() as temp_dir:
test_path = os.path.join(temp_dir, "test_ftp_dir")
print(f"📁 Testing directory creation at: {test_path}")
# Test creating a new directory
result = FTPUtilities.ftpFunctions(test_path, "testuser")
if result[0] == 1:
if os.path.exists(test_path) and os.path.isdir(test_path):
print("✅ Directory creation successful")
return True
else:
print("❌ Directory creation failed - directory not found")
return False
else:
print(f"❌ Directory creation failed: {result[1]}")
return False
except Exception as e:
print(f"❌ Directory creation test failed: {e}")
return False
def test_security_features():
"""Test security features"""
print("\n🔍 Testing Security Features...")
security_tests = [
("Path traversal prevention", ".."),
("Home directory reference prevention", "~"),
("Absolute path prevention", "/etc/passwd"),
("Command injection prevention", ";rm -rf /"),
("Pipe command prevention", "|cat /etc/passwd"),
("Background command prevention", "&reboot"),
("Command substitution prevention", "`whoami`"),
]
print("🛡️ Security Test Results:")
print("-" * 40)
for test_name, malicious_path in security_tests:
try:
# This should be caught by our validation
if any(char in malicious_path for char in ['..', '~', '/', ';', '|', '&', '`']):
print(f"{test_name}: Correctly detected malicious path")
else:
print(f"{test_name}: Failed to detect malicious path")
except Exception as e:
print(f"{test_name}: Correctly rejected with error: {e}")
return True
def main():
"""Main test function"""
print("🚀 CyberPanel FTP Account Creation Test Suite")
print("=" * 60)
# Run all tests
tests = [
("Path Validation", test_ftp_path_validation),
("Directory Creation", test_directory_creation),
("Security Features", test_security_features),
]
results = []
for test_name, test_func in tests:
print(f"\n🧪 Running {test_name} Test...")
try:
result = test_func()
results.append((test_name, result))
if result:
print(f"{test_name} Test: PASSED")
else:
print(f"{test_name} Test: FAILED")
except Exception as e:
print(f"{test_name} Test: ERROR - {e}")
results.append((test_name, False))
# Summary
print("\n" + "=" * 60)
print("📋 TEST SUMMARY")
print("=" * 60)
passed = sum(1 for _, result in results if result)
total = len(results)
for test_name, result in results:
status = "✅ PASSED" if result else "❌ FAILED"
print(f"{test_name}: {status}")
print(f"\n🎯 Overall Result: {passed}/{total} tests passed")
if passed == total:
print("🎉 All tests passed! FTP account creation should work correctly.")
return 0
else:
print("⚠️ Some tests failed. Please review the issues above.")
return 1
if __name__ == "__main__":
exit(main())