Access control for file serving

Authenticated user only for now
This commit is contained in:
2026-01-09 10:32:15 +01:00
parent 331e392fc9
commit 23a50ce4f6
6 changed files with 118 additions and 7 deletions

View File

@@ -5,7 +5,6 @@
use dokuwiki\plugin\luxtools\Path;
if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/../../../');
if (!defined('NOSESSION')) define('NOSESSION', true); // we do not use a session or authentication here (better caching)
if (!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT', 1); // we gzip ourself here
require_once(DOKU_INC . 'inc/init.php');
@@ -14,9 +13,91 @@ global $INPUT;
$syntax = plugin_load('syntax', 'luxtools');
if (!$syntax) die('plugin disabled?');
/**
* Enforce a simple allowlist based access check.
*
* The allowlist supports entries separated by commas, whitespace or newlines.
* - "alice" allows a specific user
* - "@admins" allows a DokuWiki group
*
* If the allowlist is empty, any authenticated user is allowed.
* Anonymous access is always denied.
*
* @param string $allowListRaw
* @return void
*/
function luxtools_require_access(string $allowListRaw): void
{
global $INPUT, $USERINFO;
// We need a logged-in user for any access.
$user = '';
try {
$user = (string)$INPUT->server->str('REMOTE_USER');
} catch (Throwable $e) {
$user = (string)($_SERVER['REMOTE_USER'] ?? '');
}
$user = trim($user);
if ($user === '') {
header('Content-Type: text/plain; charset=utf-8');
http_status(403);
echo 'forbidden';
exit;
}
$allowListRaw = trim($allowListRaw);
if ($allowListRaw === '') {
return; // any authenticated user
}
$tokens = preg_split('/[\s,;]+/', $allowListRaw, -1, PREG_SPLIT_NO_EMPTY);
if (!is_array($tokens) || $tokens === []) {
// If configured but empty after parsing, be conservative.
header('Content-Type: text/plain; charset=utf-8');
http_status(403);
echo 'forbidden';
exit;
}
$groups = [];
if (is_array($USERINFO) && isset($USERINFO['grps']) && is_array($USERINFO['grps'])) {
$groups = $USERINFO['grps'];
}
$allowed = false;
foreach ($tokens as $t) {
$t = trim((string)$t);
if ($t === '') continue;
if ($t[0] === '@') {
$g = trim(substr($t, 1));
if ($g !== '' && in_array($g, $groups, true)) {
$allowed = true;
break;
}
} else {
if ($t === $user) {
$allowed = true;
break;
}
}
}
if (!$allowed) {
header('Content-Type: text/plain; charset=utf-8');
http_status(403);
echo 'forbidden';
exit;
}
}
$pathUtil = new Path($syntax->getConf('paths'));
$path = $INPUT->str('root') . $INPUT->str('file');
// Enforce access before doing any filesystem work.
luxtools_require_access((string)$syntax->getConf('access_allow'));
/**
* Send a file to the client with basic caching headers.
*
@@ -47,7 +128,8 @@ function luxtools_sendfile($path, $mime, $download = false, $downloadName = null
}
if ($maxAge !== null) {
header('Cache-Control: public, max-age=' . (int)$maxAge . ', immutable');
// Authentication may apply; keep caching private.
header('Cache-Control: private, max-age=' . (int)$maxAge . ', immutable');
}
// Conditional request handling