From 242661a9c9d1b4696ec9dbd38b331cebea281c6d Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 22 Apr 2025 17:11:19 -0400 Subject: [PATCH] New Admin Panel settings (enableWebDAV & shareMaxUploadSize) --- CHANGELOG.md | 15 +++++- public/js/authModals.js | 78 ++++++++++++++++++++++------ public/webdav.php | 23 ++++++-- src/controllers/adminController.php | 46 +++++++++++----- src/controllers/folderController.php | 38 +++++++++++--- src/models/AdminModel.php | 71 +++++++++++++++++++++---- start.sh | 4 +- 7 files changed, 221 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b1e26..e3dc5d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Changes 4/22/2025 +## Changes 4/22/2025 v1.2.3 - Support for custom PUID/PGID via `PUID`/`PGID` environment variables, replacing the need to run the container with `--user` - New `PUID` and `PGID` config options in the Unraid Community Apps template @@ -9,7 +9,18 @@ - `www‑data` user is remapped at build‑time to the supplied `PUID:PGID`, then Apache drops privileges to that user - Unraid template: removed recommendation to use `--user`; replaced with `PUID`, `PGID`, and `Container Port` variables - “Permission denied” errors when forcing `--user 99:100` on Unraid by ensuring startup runs as root -- Silence group issue +- Dockerfile silence group issue +- `enableWebDAV` toggle in Admin Panel (default: disabled) +- **Admin Panel enhancements** + - New `enableWebDAV` boolean setting + - New `sharedMaxUploadSize` numeric setting (bytes) +- **Shared Folder upload size** + - `sharedMaxUploadSize` is now enforced in `FolderModel::uploadToSharedFolder` + - Upload form header on shared‑folder page dynamically shows “(X MB max size)” +- **API updates** + - `getConfig` and `updateConfig` endpoints now include `enableWebDAV` and `sharedMaxUploadSize` +- Updated `AdminModel` & `AdminController` to persist and validate new settings +- Enhanced `shareFolder()` view to pull from admin config and format the max‑upload‑size label --- diff --git a/public/js/authModals.js b/public/js/authModals.js index 6c1a029..4dd47f2 100644 --- a/public/js/authModals.js +++ b/public/js/authModals.js @@ -3,7 +3,7 @@ import { sendRequest } from './networkUtils.js'; import { t, applyTranslations, setLocale } from './i18n.js'; import { loadAdminConfigFunc } from './auth.js'; -const version = "v1.2.2"; // Update this version string as needed +const version = "v1.2.3"; // Update this version string as needed const adminTitle = `${t("admin_panel")} ${version}`; let lastLoginData = null; @@ -597,6 +597,7 @@ export function openAdminPanel() { } if (config.oidc) Object.assign(window.currentOIDCConfig, config.oidc); if (config.globalOtpauthUrl) window.currentOIDCConfig.globalOtpauthUrl = config.globalOtpauthUrl; + const isDarkMode = document.body.classList.contains("dark-mode"); const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; const modalContentStyles = ` @@ -611,6 +612,7 @@ export function openAdminPanel() { max-height: 90vh; border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"}; `; + let adminModal = document.getElementById("adminPanelModal"); if (!adminModal) { @@ -663,6 +665,28 @@ export function openAdminPanel() { + + +
+ WebDAV Access +
+ + +
+
+ + + +
+ Shared Max Upload Size (bytes) +
+ + Enter maximum bytes allowed for shared-folder uploads +
+
+ +
${t("oidc_configuration")}
@@ -698,33 +722,34 @@ export function openAdminPanel() { `; document.body.appendChild(adminModal); - // Bind closing events that will use our enhanced close function. + // Bind closing document.getElementById("closeAdminPanel").addEventListener("click", closeAdminPanel); - adminModal.addEventListener("click", (e) => { - if (e.target === adminModal) closeAdminPanel(); - }); + adminModal.addEventListener("click", e => { if (e.target === adminModal) closeAdminPanel(); }); document.getElementById("cancelAdminSettings").addEventListener("click", closeAdminPanel); - // Bind other buttons. + // Bind other buttons document.getElementById("adminOpenAddUser").addEventListener("click", () => { toggleVisibility("addUserModal", true); document.getElementById("newUsername").focus(); }); document.getElementById("adminOpenRemoveUser").addEventListener("click", () => { - if (typeof window.loadUserList === "function") { - window.loadUserList(); - } + if (typeof window.loadUserList === "function") window.loadUserList(); toggleVisibility("removeUserModal", true); }); document.getElementById("adminOpenUserPermissions").addEventListener("click", () => { openUserPermissionsModal(); }); - document.getElementById("saveAdminSettings").addEventListener("click", () => { + // Save handler + document.getElementById("saveAdminSettings").addEventListener("click", () => { const disableFormLoginCheckbox = document.getElementById("disableFormLogin"); const disableBasicAuthCheckbox = document.getElementById("disableBasicAuth"); const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin"); - const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length; + const enableWebDAVCheckbox = document.getElementById("enableWebDAV"); + const sharedMaxUploadSizeInput = document.getElementById("sharedMaxUploadSize"); + + const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox] + .filter(cb => cb.checked).length; if (totalDisabled === 3) { showToast(t("at_least_one_login_method")); disableOIDCLoginCheckbox.checked = false; @@ -738,8 +763,8 @@ export function openAdminPanel() { } return; } - const newHeaderTitle = document.getElementById("headerTitle").value.trim(); + const newHeaderTitle = document.getElementById("headerTitle").value.trim(); const newOIDCConfig = { providerUrl: document.getElementById("oidcProviderUrl").value.trim(), clientId: document.getElementById("oidcClientId").value.trim(), @@ -749,13 +774,18 @@ export function openAdminPanel() { const disableFormLogin = disableFormLoginCheckbox.checked; const disableBasicAuth = disableBasicAuthCheckbox.checked; const disableOIDCLogin = disableOIDCLoginCheckbox.checked; + const enableWebDAV = enableWebDAVCheckbox.checked; + const sharedMaxUploadSize = parseInt(sharedMaxUploadSizeInput.value, 10) || 0; const globalOtpauthUrl = document.getElementById("globalOtpauthUrl").value.trim(); + sendRequest("/api/admin/updateConfig.php", "POST", { header_title: newHeaderTitle, oidc: newOIDCConfig, disableFormLogin, disableBasicAuth, disableOIDCLogin, + enableWebDAV, + sharedMaxUploadSize, globalOtpauthUrl }, { "X-CSRF-Token": window.csrfToken }) .then(response => { @@ -764,26 +794,32 @@ export function openAdminPanel() { localStorage.setItem("disableFormLogin", disableFormLogin); localStorage.setItem("disableBasicAuth", disableBasicAuth); localStorage.setItem("disableOIDCLogin", disableOIDCLogin); + localStorage.setItem("enableWebDAV", enableWebDAV); + localStorage.setItem("sharedMaxUploadSize", sharedMaxUploadSize); if (typeof window.updateLoginOptionsUI === "function") { - window.updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin }); + window.updateLoginOptionsUI({ + disableFormLogin, + disableBasicAuth, + disableOIDCLogin + }); } - // Update the captured initial state since the changes have now been saved. captureInitialAdminConfig(); closeAdminPanel(); loadAdminConfigFunc(); - } else { showToast(t("error_updating_settings") + ": " + (response.error || t("unknown_error"))); } }) .catch(() => { }); }); + // Enforce login option constraints. const disableFormLoginCheckbox = document.getElementById("disableFormLogin"); const disableBasicAuthCheckbox = document.getElementById("disableBasicAuth"); const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin"); function enforceLoginOptionConstraint(changedCheckbox) { - const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length; + const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox] + .filter(cb => cb.checked).length; if (changedCheckbox.checked && totalDisabled === 3) { showToast(t("at_least_one_login_method")); changedCheckbox.checked = false; @@ -793,13 +829,17 @@ export function openAdminPanel() { disableBasicAuthCheckbox.addEventListener("change", function () { enforceLoginOptionConstraint(this); }); disableOIDCLoginCheckbox.addEventListener("change", function () { enforceLoginOptionConstraint(this); }); + // Initial checkbox and input states document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; + document.getElementById("enableWebDAV").checked = config.enableWebDAV === true; + document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize || ""; - // Capture initial state after the modal loads. captureInitialAdminConfig(); + } else { + // Update existing modal and show adminModal.style.backgroundColor = overlayBackground; const modalContent = adminModal.querySelector(".modal-content"); if (modalContent) { @@ -815,6 +855,8 @@ export function openAdminPanel() { document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; + document.getElementById("enableWebDAV").checked = config.enableWebDAV === true; + document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize || ""; adminModal.style.display = "flex"; captureInitialAdminConfig(); } @@ -837,6 +879,8 @@ export function openAdminPanel() { document.getElementById("disableFormLogin").checked = localStorage.getItem("disableFormLogin") === "true"; document.getElementById("disableBasicAuth").checked = localStorage.getItem("disableBasicAuth") === "true"; document.getElementById("disableOIDCLogin").checked = localStorage.getItem("disableOIDCLogin") === "true"; + document.getElementById("enableWebDAV").checked = localStorage.getItem("enableWebDAV") === "true"; + document.getElementById("sharedMaxUploadSize").value = localStorage.getItem("sharedMaxUploadSize") || ""; adminModal.style.display = "flex"; captureInitialAdminConfig(); } else { diff --git a/public/webdav.php b/public/webdav.php index 62d68a7..1433262 100644 --- a/public/webdav.php +++ b/public/webdav.php @@ -1,6 +1,7 @@ addPlugin($authPlugin); -//$server->addPlugin(new BrowserPlugin()); // optional HTML browser UI $server->addPlugin( new LocksPlugin( new LocksFileBackend(sys_get_temp_dir() . '/sabre-locksdb') diff --git a/src/controllers/adminController.php b/src/controllers/adminController.php index 6df3651..6f75d40 100644 --- a/src/controllers/adminController.php +++ b/src/controllers/adminController.php @@ -35,7 +35,9 @@ class AdminController * @OA\Property(property="disableBasicAuth", type="boolean", example=false), * @OA\Property(property="disableOIDCLogin", type="boolean", example=false) * ), - * @OA\Property(property="globalOtpauthUrl", type="string", example="") + * @OA\Property(property="globalOtpauthUrl", type="string", example=""), + * @OA\Property(property="enableWebDAV", type="boolean", example=false), + * @OA\Property(property="sharedMaxUploadSize", type="integer", example=52428800) * ) * ), * @OA\Response( @@ -88,7 +90,9 @@ class AdminController * @OA\Property(property="disableBasicAuth", type="boolean", example=false), * @OA\Property(property="disableOIDCLogin", type="boolean", example=false) * ), - * @OA\Property(property="globalOtpauthUrl", type="string", example="") + * @OA\Property(property="globalOtpauthUrl", type="string", example=""), + * @OA\Property(property="enableWebDAV", type="boolean", example=false), + * @OA\Property(property="sharedMaxUploadSize", type="integer", example=52428800) * ) * ), * @OA\Response( @@ -149,7 +153,7 @@ class AdminController exit; } - // Prepare configuration array. + // Prepare existing settings $headerTitle = isset($data['header_title']) ? trim($data['header_title']) : ""; $oidc = isset($data['oidc']) ? $data['oidc'] : []; $oidcProviderUrl = isset($oidc['providerUrl']) ? filter_var($oidc['providerUrl'], FILTER_SANITIZE_URL) : ''; @@ -183,20 +187,38 @@ class AdminController } $globalOtpauthUrl = isset($data['globalOtpauthUrl']) ? trim($data['globalOtpauthUrl']) : ""; + // ── NEW: enableWebDAV flag ────────────────────────────────────── + $enableWebDAV = false; + if (array_key_exists('enableWebDAV', $data)) { + $enableWebDAV = filter_var($data['enableWebDAV'], FILTER_VALIDATE_BOOLEAN); + } elseif (isset($data['features']['enableWebDAV'])) { + $enableWebDAV = filter_var($data['features']['enableWebDAV'], FILTER_VALIDATE_BOOLEAN); + } + + // ── NEW: sharedMaxUploadSize ────────────────────────────────────── + $sharedMaxUploadSize = null; + if (array_key_exists('sharedMaxUploadSize', $data)) { + $sharedMaxUploadSize = filter_var($data['sharedMaxUploadSize'], FILTER_VALIDATE_INT); + } elseif (isset($data['features']['sharedMaxUploadSize'])) { + $sharedMaxUploadSize = filter_var($data['features']['sharedMaxUploadSize'], FILTER_VALIDATE_INT); + } + $configUpdate = [ - 'header_title' => $headerTitle, - 'oidc' => [ - 'providerUrl' => $oidcProviderUrl, - 'clientId' => $oidcClientId, - 'clientSecret' => $oidcClientSecret, - 'redirectUri' => $oidcRedirectUri, + 'header_title' => $headerTitle, + 'oidc' => [ + 'providerUrl' => $oidcProviderUrl, + 'clientId' => $oidcClientId, + 'clientSecret' => $oidcClientSecret, + 'redirectUri' => $oidcRedirectUri, ], - 'loginOptions' => [ + 'loginOptions' => [ 'disableFormLogin' => $disableFormLogin, 'disableBasicAuth' => $disableBasicAuth, 'disableOIDCLogin' => $disableOIDCLogin, ], - 'globalOtpauthUrl' => $globalOtpauthUrl + 'globalOtpauthUrl' => $globalOtpauthUrl, + 'enableWebDAV' => $enableWebDAV, + 'sharedMaxUploadSize' => $sharedMaxUploadSize // ← NEW ]; // Delegate to the model. @@ -207,4 +229,4 @@ class AdminController echo json_encode($result); exit; } -} +} \ No newline at end of file diff --git a/src/controllers/folderController.php b/src/controllers/folderController.php index aa87c8b..97401e1 100644 --- a/src/controllers/folderController.php +++ b/src/controllers/folderController.php @@ -401,6 +401,20 @@ class FolderController * * @return void Outputs HTML content. */ + + function formatBytes($bytes) + { + if ($bytes < 1024) { + return $bytes . " B"; + } elseif ($bytes < 1024 * 1024) { + return round($bytes / 1024, 2) . " KB"; + } elseif ($bytes < 1024 * 1024 * 1024) { + return round($bytes / (1024 * 1024), 2) . " MB"; + } else { + return round($bytes / (1024 * 1024 * 1024), 2) . " GB"; + } + } + public function shareFolder(): void { // Retrieve GET parameters. @@ -495,12 +509,14 @@ class FolderController exit; } - // Extract data for the HTML view. - $folderName = $data['folder']; - $files = $data['files']; - $currentPage = $data['currentPage']; - $totalPages = $data['totalPages']; + // Load admin config so we can pull the sharedMaxUploadSize + require_once PROJECT_ROOT . '/src/models/AdminModel.php'; + $adminConfig = AdminModel::getConfig(); + $sharedMaxUploadSize = isset($adminConfig['sharedMaxUploadSize']) && is_numeric($adminConfig['sharedMaxUploadSize']) + ? (int)$adminConfig['sharedMaxUploadSize'] + : null; + // For human‐readable formatting function formatBytes($bytes) { if ($bytes < 1024) { @@ -514,6 +530,12 @@ class FolderController } } + // Extract data for the HTML view. + $folderName = $data['folder']; + $files = $data['files']; + $currentPage = $data['currentPage']; + $totalPages = $data['totalPages']; + // Build the HTML view. header("Content-Type: text/html; charset=utf-8"); ?> @@ -717,7 +739,11 @@ class FolderController
-

Upload File (50mb max size)

+

Upload File + + ( max size) + +

diff --git a/src/models/AdminModel.php b/src/models/AdminModel.php index f48ba49..682aca6 100644 --- a/src/models/AdminModel.php +++ b/src/models/AdminModel.php @@ -5,6 +5,23 @@ require_once PROJECT_ROOT . '/config/config.php'; class AdminModel { + /** + * Parse a shorthand size value (e.g. "5G", "500M", "123K") into bytes. + * + * @param string $val + * @return int + */ + private static function parseSize(string $val): int + { + $unit = strtolower(substr($val, -1)); + $num = (int) rtrim($val, 'bkmgtpezyBKMGTPESY'); + switch ($unit) { + case 'g': return $num * 1024 ** 3; + case 'm': return $num * 1024 ** 2; + case 'k': return $num * 1024; + default: return $num; + } + } /** * Updates the admin configuration file. @@ -24,6 +41,28 @@ class AdminModel return ["error" => "Incomplete OIDC configuration."]; } + // Ensure enableWebDAV flag is boolean (default to false if missing) + $configUpdate['enableWebDAV'] = isset($configUpdate['enableWebDAV']) + ? (bool)$configUpdate['enableWebDAV'] + : false; + + // Validate sharedMaxUploadSize if provided + if (isset($configUpdate['sharedMaxUploadSize'])) { + $sms = filter_var( + $configUpdate['sharedMaxUploadSize'], + FILTER_VALIDATE_INT, + ["options" => ["min_range" => 1]] + ); + if ($sms === false) { + return ["error" => "Invalid sharedMaxUploadSize."]; + } + $totalBytes = self::parseSize(TOTAL_UPLOAD_SIZE); + if ($sms > $totalBytes) { + return ["error" => "sharedMaxUploadSize must be ≤ TOTAL_UPLOAD_SIZE."]; + } + $configUpdate['sharedMaxUploadSize'] = $sms; + } + // Convert configuration to JSON. $plainTextConfig = json_encode($configUpdate, JSON_PRETTY_PRINT); if ($plainTextConfig === false) { @@ -59,7 +98,8 @@ class AdminModel * * @return array The configuration array, or defaults if not found. */ - public static function getConfig(): array { + public static function getConfig(): array + { $configFile = USERS_DIR . 'adminConfig.json'; if (file_exists($configFile)) { $encryptedContent = file_get_contents($configFile); @@ -72,10 +112,9 @@ class AdminModel if (!is_array($config)) { $config = []; } - - // Normalize login options. + + // Normalize login options if missing if (!isset($config['loginOptions'])) { - // Create loginOptions array from top-level keys if missing. $config['loginOptions'] = [ 'disableFormLogin' => isset($config['disableFormLogin']) ? (bool)$config['disableFormLogin'] : false, 'disableBasicAuth' => isset($config['disableBasicAuth']) ? (bool)$config['disableBasicAuth'] : false, @@ -88,31 +127,43 @@ class AdminModel $config['loginOptions']['disableBasicAuth'] = (bool)$config['loginOptions']['disableBasicAuth']; $config['loginOptions']['disableOIDCLogin'] = (bool)$config['loginOptions']['disableOIDCLogin']; } - + + // Default values for other keys if (!isset($config['globalOtpauthUrl'])) { $config['globalOtpauthUrl'] = ""; } if (!isset($config['header_title']) || empty($config['header_title'])) { $config['header_title'] = "FileRise"; } + if (!isset($config['enableWebDAV'])) { + $config['enableWebDAV'] = false; + } + // Default sharedMaxUploadSize to 50MB or TOTAL_UPLOAD_SIZE if smaller + if (!isset($config['sharedMaxUploadSize'])) { + $defaultSms = min(50 * 1024 * 1024, self::parseSize(TOTAL_UPLOAD_SIZE)); + $config['sharedMaxUploadSize'] = $defaultSms; + } + return $config; } else { // Return defaults. return [ - 'header_title' => "FileRise", - 'oidc' => [ + 'header_title' => "FileRise", + 'oidc' => [ 'providerUrl' => 'https://your-oidc-provider.com', 'clientId' => 'YOUR_CLIENT_ID', 'clientSecret' => 'YOUR_CLIENT_SECRET', 'redirectUri' => 'https://yourdomain.com/api/auth/auth.php?oidc=callback' ], - 'loginOptions' => [ + 'loginOptions' => [ 'disableFormLogin' => false, 'disableBasicAuth' => false, 'disableOIDCLogin' => false ], - 'globalOtpauthUrl' => "" + 'globalOtpauthUrl' => "", + 'enableWebDAV' => false, + 'sharedMaxUploadSize' => min(50 * 1024 * 1024, self::parseSize(TOTAL_UPLOAD_SIZE)) ]; } } -} +} \ No newline at end of file diff --git a/start.sh b/start.sh index 61cf610..12b6b51 100644 --- a/start.sh +++ b/start.sh @@ -152,8 +152,8 @@ find /var/www -type f -exec chmod 664 {} \; find /var/www -type d -exec chmod 775 {} \; chown -R ${PUID:-99}:${PGID:-100} /var/www -echo "🔥 Final PHP configuration (90-custom.ini):" -cat /etc/php/8.3/apache2/conf.d/90-custom.ini +echo "🔥 Final PHP configuration (99-custom.ini):" +cat /etc/php/8.3/apache2/conf.d/99-custom.ini echo "🔥 Final Apache configuration (limit_request_body.conf):" cat /etc/apache2/conf-enabled/limit_request_body.conf