mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-06 09:25:54 +02:00
fix: LPMA launch routes in secMiddleware; strict sign-on + lpma_policy_read
- secMiddleware: allow Limited phpMyAdmin launch URLs and phpmyadminsignin without JSON-body filtering that breaks sign-on POSTs - plogical/public phpmyadminsignin: lpma_policy_read.inc.php, strict cookie helpers - webmail: section header comments only
This commit is contained in:
@@ -38,8 +38,13 @@ class secMiddleware:
|
||||
import re
|
||||
webhook_pattern = re.compile(r'^/websites/[^/]+/(webhook|gitNotify)/?$')
|
||||
|
||||
if pathActual == "/backup/localInitiate" or pathActual == '/' or pathActual == '/verifyLogin' or pathActual == '/logout' or pathActual.startswith('/api')\
|
||||
or webhook_pattern.match(pathActual) or pathActual.startswith('/cloudAPI') or pathActual.startswith('/static/'):
|
||||
# Public one-time phpMyAdmin launch links (limitedPhpmyAdmin plugin); must work when admin is logged out.
|
||||
_lpma_public_launch = pathActual.startswith('/plugins/limitedPhpmyAdmin/launch/')
|
||||
_lpma_pma_signon = pathActual == '/phpmyadmin/phpmyadminsignin.php'
|
||||
|
||||
if pathActual == "/backup/localInitiate" or pathActual == '/' or pathActual == '/verifyLogin' or pathActual == '/logout' or pathActual.startswith('/api')\
|
||||
or webhook_pattern.match(pathActual) or pathActual.startswith('/cloudAPI') or pathActual.startswith('/static/')\
|
||||
or _lpma_public_launch or _lpma_pma_signon:
|
||||
pass
|
||||
else:
|
||||
# Session check logging removed
|
||||
@@ -108,6 +113,11 @@ class secMiddleware:
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
# phpMyAdmin sign-on POST carries MySQL password; skip character filter (may contain $ ( ) etc.).
|
||||
if pathActual == '/phpmyadmin/phpmyadminsignin.php':
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
# logging.writeToFile(request.body)
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
|
||||
134
plogical/lpma_policy_read.inc.php
Normal file
134
plogical/lpma_policy_read.inc.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* Load Limited phpMyAdmin UI policy (strict mode + blocked preference tabs).
|
||||
* Primary: pluginState (writable by cyberpanel). Fallbacks for older installs.
|
||||
*/
|
||||
function lpma_read_limited_policy(): array
|
||||
{
|
||||
$defaultBlocked = [
|
||||
'manage' => true,
|
||||
'two_factor' => true,
|
||||
'features' => true,
|
||||
'sql' => true,
|
||||
'navigation' => true,
|
||||
'main_panel' => true,
|
||||
'export' => true,
|
||||
'import' => true,
|
||||
];
|
||||
$policy = [
|
||||
'strict_mode' => true,
|
||||
'blocked_tabs' => $defaultBlocked,
|
||||
];
|
||||
$paths = [
|
||||
'/usr/local/CyberCP/pluginState/limited_phpmyadmin_policy.json',
|
||||
'/var/lib/cyberpanel-panelstate/limited_phpmyadmin_policy.json',
|
||||
'/etc/cyberpanel/limited_phpmyadmin_policy.json',
|
||||
];
|
||||
foreach ($paths as $policyPath) {
|
||||
if (! @is_readable($policyPath)) {
|
||||
continue;
|
||||
}
|
||||
$raw = @file_get_contents($policyPath);
|
||||
if ($raw === false) {
|
||||
continue;
|
||||
}
|
||||
$decoded = @json_decode($raw, true);
|
||||
if (! is_array($decoded)) {
|
||||
continue;
|
||||
}
|
||||
$policy['strict_mode'] = isset($decoded['strict_mode']) ? (bool) $decoded['strict_mode'] : true;
|
||||
if (isset($decoded['blocked_tabs']) && is_array($decoded['blocked_tabs'])) {
|
||||
foreach ($defaultBlocked as $k => $_v) {
|
||||
$policy['blocked_tabs'][$k] = isset($decoded['blocked_tabs'][$k])
|
||||
? (bool) $decoded['blocked_tabs'][$k]
|
||||
: true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $policy;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if a cpma_* request to this application route must be turned away (Settings prefs + main menu targets).
|
||||
* Does not block table browse at route "/sql" (that is Browse, not the SQL runner).
|
||||
*/
|
||||
function lpma_cpma_route_blocked(string $requestedRoute, array $policy): bool
|
||||
{
|
||||
if ($requestedRoute === '') {
|
||||
return false;
|
||||
}
|
||||
$bt = $policy['blocked_tabs'] ?? [];
|
||||
$blocked = static function (string $k) use ($bt): bool {
|
||||
return (($bt[$k] ?? true) === true);
|
||||
};
|
||||
|
||||
if (strpos($requestedRoute, '/preferences') === 0) {
|
||||
$routeToTab = [
|
||||
'/preferences/manage' => 'manage',
|
||||
'/preferences/two-factor' => 'two_factor',
|
||||
'/preferences/features' => 'features',
|
||||
'/preferences/sql' => 'sql',
|
||||
'/preferences/navigation' => 'navigation',
|
||||
'/preferences/main-panel' => 'main_panel',
|
||||
'/preferences/export' => 'export',
|
||||
'/preferences/import' => 'import',
|
||||
];
|
||||
if (isset($routeToTab[$requestedRoute])) {
|
||||
return $blocked($routeToTab[$requestedRoute]);
|
||||
}
|
||||
|
||||
return (($policy['strict_mode'] ?? true) === true);
|
||||
}
|
||||
|
||||
if ($blocked('sql')) {
|
||||
if (preg_match('#^/(server|database|table)/sql$#', $requestedRoute) === 1) {
|
||||
return true;
|
||||
}
|
||||
if ($requestedRoute === '/database/multi-table-query' || $requestedRoute === '/database/qbe') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($blocked('export') && preg_match('#^/(server|database|table)/export$#', $requestedRoute) === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($blocked('import') && preg_match('#^/(server|database|table)/import$#', $requestedRoute) === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($blocked('main_panel')) {
|
||||
if (
|
||||
$requestedRoute === '/server/databases'
|
||||
|| $requestedRoute === '/server/variables'
|
||||
|| $requestedRoute === '/server/collations'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (strpos($requestedRoute, '/server/status') === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($blocked('features')) {
|
||||
if (
|
||||
$requestedRoute === '/server/engines'
|
||||
|| $requestedRoute === '/server/plugins'
|
||||
|| $requestedRoute === '/server/binlog'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
$requestedRoute === '/database/designer'
|
||||
|| $requestedRoute === '/database/central-columns'
|
||||
|| $requestedRoute === '/database/tracking'
|
||||
|| $requestedRoute === '/table/tracking'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -3,10 +3,28 @@
|
||||
|
||||
define("PMA_SIGNON_INDEX", 1);
|
||||
|
||||
require_once __DIR__ . '/lpma_policy_read.inc.php';
|
||||
|
||||
try {
|
||||
define('PMA_SIGNON_SESSIONNAME', 'SignonSession');
|
||||
define('PMA_DISABLE_SSL_PEER_VALIDATION', TRUE);
|
||||
|
||||
function lpma_set_strict_cookie($enabled) {
|
||||
$opts = array(
|
||||
'expires' => $enabled ? (time() + 86400) : (time() - 86400),
|
||||
'path' => '/phpmyadmin/',
|
||||
'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
);
|
||||
setcookie('PMA_LPMA_STRICT', $enabled ? '1' : '', $opts);
|
||||
}
|
||||
|
||||
function lpma_global_strict_mode_enabled() {
|
||||
$p = lpma_read_limited_policy();
|
||||
return ! empty($p['strict_mode']);
|
||||
}
|
||||
|
||||
// Handle both GET and POST parameters for token and username
|
||||
$token = isset($_POST['token']) ? $_POST['token'] : (isset($_GET['token']) ? $_GET['token'] : null);
|
||||
$username = isset($_POST['username']) ? $_POST['username'] : (isset($_GET['username']) ? $_GET['username'] : null);
|
||||
@@ -32,6 +50,7 @@ try {
|
||||
echo '<script>document.getElementById("redirectForm").submit();</script>';
|
||||
|
||||
} else if (isset($_POST['logout']) || isset($_GET['logout'])) {
|
||||
lpma_set_strict_cookie(false);
|
||||
session_name(PMA_SIGNON_SESSIONNAME);
|
||||
@session_start();
|
||||
$_SESSION = array();
|
||||
@@ -47,9 +66,14 @@ try {
|
||||
|
||||
$username = htmlspecialchars($_POST['username'], ENT_QUOTES, 'UTF-8');
|
||||
$password = $_POST['password'];
|
||||
$strictMode = (isset($_POST['lpma_strict']) && $_POST['lpma_strict'] === '1');
|
||||
$isLimitedUser = (strpos($username, 'cpma_') === 0);
|
||||
$host = isset($_POST['host']) ? trim($_POST['host']) : '127.0.0.1';
|
||||
if ($host === 'localhost') { $host = '127.0.0.1'; }
|
||||
|
||||
$effectiveStrictMode = ($strictMode || lpma_global_strict_mode_enabled()) && $isLimitedUser;
|
||||
lpma_set_strict_cookie($effectiveStrictMode);
|
||||
|
||||
$_SESSION['PMA_single_signon_user'] = $username;
|
||||
$_SESSION['PMA_single_signon_password'] = $password;
|
||||
$_SESSION['PMA_single_signon_host'] = $host;
|
||||
|
||||
@@ -3,10 +3,39 @@
|
||||
|
||||
define("PMA_SIGNON_INDEX", 1);
|
||||
|
||||
// Policy helper ships in plogical/ (same layout as phpmyadmin index.php)
|
||||
$_lpma_policy = dirname(dirname(__DIR__)) . '/plogical/lpma_policy_read.inc.php';
|
||||
if (is_readable($_lpma_policy)) {
|
||||
require_once $_lpma_policy;
|
||||
} elseif (is_readable(__DIR__ . '/lpma_policy_read.inc.php')) {
|
||||
require_once __DIR__ . '/lpma_policy_read.inc.php';
|
||||
} else {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'phpMyAdmin sign-on is misconfigured: lpma_policy_read.inc.php is missing.';
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
define('PMA_SIGNON_SESSIONNAME', 'SignonSession');
|
||||
define('PMA_DISABLE_SSL_PEER_VALIDATION', TRUE);
|
||||
|
||||
function lpma_set_strict_cookie($enabled) {
|
||||
$opts = array(
|
||||
'expires' => $enabled ? (time() + 86400) : (time() - 86400),
|
||||
'path' => '/phpmyadmin/',
|
||||
'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
);
|
||||
setcookie('PMA_LPMA_STRICT', $enabled ? '1' : '', $opts);
|
||||
}
|
||||
|
||||
function lpma_global_strict_mode_enabled() {
|
||||
$p = lpma_read_limited_policy();
|
||||
return ! empty($p['strict_mode']);
|
||||
}
|
||||
|
||||
// Handle both GET and POST parameters for token and username
|
||||
$token = isset($_POST['token']) ? $_POST['token'] : (isset($_GET['token']) ? $_GET['token'] : null);
|
||||
$username = isset($_POST['username']) ? $_POST['username'] : (isset($_GET['username']) ? $_GET['username'] : null);
|
||||
@@ -32,6 +61,7 @@ try {
|
||||
echo '<script>document.getElementById("redirectForm").submit();</script>';
|
||||
|
||||
} else if (isset($_POST['logout']) || isset($_GET['logout'])) {
|
||||
lpma_set_strict_cookie(false);
|
||||
session_name(PMA_SIGNON_SESSIONNAME);
|
||||
@session_start();
|
||||
$_SESSION = array();
|
||||
@@ -47,9 +77,14 @@ try {
|
||||
|
||||
$username = htmlspecialchars($_POST['username'], ENT_QUOTES, 'UTF-8');
|
||||
$password = $_POST['password'];
|
||||
$strictMode = (isset($_POST['lpma_strict']) && $_POST['lpma_strict'] === '1');
|
||||
$isLimitedUser = (strpos($username, 'cpma_') === 0);
|
||||
$host = isset($_POST['host']) ? trim($_POST['host']) : '127.0.0.1';
|
||||
if ($host === 'localhost') { $host = '127.0.0.1'; }
|
||||
|
||||
$effectiveStrictMode = ($strictMode || lpma_global_strict_mode_enabled()) && $isLimitedUser;
|
||||
lpma_set_strict_cookie($effectiveStrictMode);
|
||||
|
||||
$_SESSION['PMA_single_signon_user'] = $username;
|
||||
$_SESSION['PMA_single_signon_password'] = $password;
|
||||
$_SESSION['PMA_single_signon_host'] = $host;
|
||||
|
||||
@@ -98,7 +98,9 @@ class IMAPClient:
|
||||
def _folder_type(self, folder_name):
|
||||
"""Identify special folder type for UI (icons, sidebar grouping).
|
||||
|
||||
CyberPanel/Dovecot uses INBOX.* names; accounts may also use Spam, Trash, etc.
|
||||
CyberPanel/Dovecot uses INBOX.* names, but some accounts also have
|
||||
INBOX.spam, Trash, Archive, etc. Classify those so they are not treated
|
||||
as generic user folders.
|
||||
"""
|
||||
fn = (folder_name or '').strip()
|
||||
if not fn:
|
||||
|
||||
@@ -5,7 +5,8 @@ from loginSystem.views import loadLoginPage
|
||||
from .webmailManager import WebmailManager
|
||||
|
||||
|
||||
# --- Page Views ---
|
||||
# ── Page Views ────────────────────────────────────────────────
|
||||
|
||||
def loadWebmail(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -23,7 +24,8 @@ def loadLogin(request):
|
||||
return wm.loadLogin()
|
||||
|
||||
|
||||
# --- Auth APIs ---
|
||||
# ── Auth APIs ─────────────────────────────────────────────────
|
||||
|
||||
def apiLogin(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -70,7 +72,8 @@ def apiSwitchAccount(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Folder APIs ---
|
||||
# ── Folder APIs ───────────────────────────────────────────────
|
||||
|
||||
def apiListFolders(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -111,7 +114,8 @@ def apiDeleteFolder(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Message APIs ---
|
||||
# ── Message APIs ──────────────────────────────────────────────
|
||||
|
||||
def apiListMessages(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -152,7 +156,8 @@ def apiGetAttachment(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Action APIs ---
|
||||
# ── Action APIs ───────────────────────────────────────────────
|
||||
|
||||
def apiSendMessage(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -223,7 +228,8 @@ def apiMarkFlagged(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Contact APIs ---
|
||||
# ── Contact APIs ──────────────────────────────────────────────
|
||||
|
||||
def apiListContacts(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -323,7 +329,8 @@ def apiImportRulesFromSnappymail(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Sieve Rule APIs ---
|
||||
# ── Sieve Rule APIs ──────────────────────────────────────────
|
||||
|
||||
def apiListRules(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -374,7 +381,8 @@ def apiActivateRules(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Settings APIs ---
|
||||
# ── Settings APIs ─────────────────────────────────────────────
|
||||
|
||||
def apiGetSettings(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -395,7 +403,8 @@ def apiSaveSettings(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Image Proxy ---
|
||||
# ── Image Proxy ───────────────────────────────────────────────
|
||||
|
||||
def apiProxyImage(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -404,7 +413,8 @@ def apiProxyImage(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Helpers ---
|
||||
# ── Helpers ───────────────────────────────────────────────────
|
||||
|
||||
def _error_response(e):
|
||||
data = {'status': 0, 'error_message': str(e)}
|
||||
return HttpResponse(json.dumps(data), content_type='application/json')
|
||||
|
||||
Reference in New Issue
Block a user