This commit is contained in:
79
public/api/admin/acl/getGrants.php
Normal file
79
public/api/admin/acl/getGrants.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// public/api/admin/acl/getGrants.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../../../config/config.php';
|
||||
require_once PROJECT_ROOT . '/src/lib/ACL.php';
|
||||
require_once PROJECT_ROOT . '/src/models/FolderModel.php';
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Admin only
|
||||
if (empty($_SESSION['authenticated']) || empty($_SESSION['isAdmin'])) {
|
||||
http_response_code(401); echo json_encode(['error'=>'Unauthorized']); exit;
|
||||
}
|
||||
|
||||
$user = trim((string)($_GET['user'] ?? ''));
|
||||
if ($user === '' || !preg_match(REGEX_USER, $user)) {
|
||||
http_response_code(400); echo json_encode(['error'=>'Invalid user']); exit;
|
||||
}
|
||||
|
||||
// Build the folder list (admin sees all)
|
||||
$folders = [];
|
||||
try {
|
||||
$rows = FolderModel::getFolderList();
|
||||
if (is_array($rows)) {
|
||||
foreach ($rows as $r) {
|
||||
$f = is_array($r) ? ($r['folder'] ?? '') : (string)$r;
|
||||
if ($f !== '') $folders[$f] = true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) { /* ignore */ }
|
||||
|
||||
if (empty($folders)) {
|
||||
$aclPath = META_DIR . 'folder_acl.json';
|
||||
if (is_file($aclPath)) {
|
||||
$data = json_decode((string)@file_get_contents($aclPath), true);
|
||||
if (is_array($data['folders'] ?? null)) {
|
||||
foreach ($data['folders'] as $name => $_) $folders[$name] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$folderList = array_keys($folders);
|
||||
if (!in_array('root', $folderList, true)) array_unshift($folderList, 'root');
|
||||
|
||||
$has = function(array $arr, string $u): bool {
|
||||
foreach ($arr as $x) if (strcasecmp((string)$x, $u) === 0) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
$out = [];
|
||||
foreach ($folderList as $f) {
|
||||
$rec = ACL::explicit($f); // owners, read, write, share, read_own
|
||||
|
||||
$isOwner = $has($rec['owners'], $user);
|
||||
$canUpload = $isOwner || $has($rec['write'], $user);
|
||||
|
||||
// IMPORTANT: full view only if owner or explicit read
|
||||
$canViewAll = $isOwner || $has($rec['read'], $user);
|
||||
|
||||
// own-only view reflects explicit read_own (we keep it separate even if they have full view)
|
||||
$canViewOwn = $has($rec['read_own'], $user);
|
||||
|
||||
// Share only if owner or explicit share
|
||||
$canShare = $isOwner || $has($rec['share'], $user);
|
||||
|
||||
if ($canViewAll || $canViewOwn || $canUpload || $isOwner || $canShare) {
|
||||
$out[$f] = [
|
||||
'view' => $canViewAll,
|
||||
'viewOwn' => $canViewOwn,
|
||||
'upload' => $canUpload,
|
||||
'manage' => $isOwner,
|
||||
'share' => $canShare,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['grants' => $out], JSON_UNESCAPED_SLASHES);
|
||||
105
public/api/admin/acl/saveGrants.php
Normal file
105
public/api/admin/acl/saveGrants.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
// public/api/admin/acl/saveGrants.php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../../../config/config.php';
|
||||
require_once PROJECT_ROOT . '/src/lib/ACL.php';
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// ---- Auth + CSRF -----------------------------------------------------------
|
||||
if (empty($_SESSION['authenticated']) || empty($_SESSION['isAdmin'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$headers = function_exists('getallheaders') ? array_change_key_case(getallheaders(), CASE_LOWER) : [];
|
||||
$csrf = trim($headers['x-csrf-token'] ?? ($_POST['csrfToken'] ?? ''));
|
||||
|
||||
if (empty($_SESSION['csrf_token']) || $csrf !== $_SESSION['csrf_token']) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid CSRF token']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---- Helpers ---------------------------------------------------------------
|
||||
/**
|
||||
* Sanitize a grants map to allowed flags only:
|
||||
* view | viewOwn | upload | manage | share
|
||||
*/
|
||||
function sanitize_grants_map(array $grants): array {
|
||||
$allowed = ['view','viewOwn','upload','manage','share'];
|
||||
$out = [];
|
||||
foreach ($grants as $folder => $caps) {
|
||||
if (!is_string($folder)) $folder = (string)$folder;
|
||||
if (!is_array($caps)) $caps = [];
|
||||
$row = [];
|
||||
foreach ($allowed as $k) {
|
||||
$row[$k] = !empty($caps[$k]);
|
||||
}
|
||||
// include folder even if all false (signals "remove all for this user on this folder")
|
||||
$out[$folder] = $row;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
function valid_user(string $u): bool {
|
||||
return ($u !== '' && preg_match(REGEX_USER, $u));
|
||||
}
|
||||
|
||||
// ---- Read JSON body --------------------------------------------------------
|
||||
$raw = file_get_contents('php://input');
|
||||
$in = json_decode((string)$raw, true);
|
||||
if (!is_array($in)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid JSON']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---- Single user mode: { user, grants } ------------------------------------
|
||||
if (isset($in['user']) && isset($in['grants']) && is_array($in['grants'])) {
|
||||
$user = trim((string)$in['user']);
|
||||
if (!valid_user($user)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid user']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$grants = sanitize_grants_map($in['grants']);
|
||||
|
||||
try {
|
||||
$res = ACL::applyUserGrantsAtomic($user, $grants);
|
||||
echo json_encode($res, JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Failed to save grants', 'detail' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Batch mode: { changes: [ { user, grants }, ... ] } --------------------
|
||||
if (isset($in['changes']) && is_array($in['changes'])) {
|
||||
$updated = [];
|
||||
foreach ($in['changes'] as $chg) {
|
||||
if (!is_array($chg)) continue;
|
||||
$user = trim((string)($chg['user'] ?? ''));
|
||||
$gr = $chg['grants'] ?? null;
|
||||
if (!valid_user($user) || !is_array($gr)) continue;
|
||||
|
||||
try {
|
||||
$res = ACL::applyUserGrantsAtomic($user, sanitize_grants_map($gr));
|
||||
$updated[$user] = $res['updated'] ?? [];
|
||||
} catch (Throwable $e) {
|
||||
$updated[$user] = ['error' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
echo json_encode(['ok' => true, 'updated' => $updated], JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---- Fallback --------------------------------------------------------------
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid payload: expected {user,grants} or {changes:[{user,grants}]}']);
|
||||
120
public/api/folder/capabilities.php
Normal file
120
public/api/folder/capabilities.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
// public/api/folder/capabilities.php
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
require_once PROJECT_ROOT . '/src/lib/ACL.php';
|
||||
require_once PROJECT_ROOT . '/src/models/UserModel.php';
|
||||
require_once PROJECT_ROOT . '/src/models/FolderModel.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// --- auth ---
|
||||
$username = $_SESSION['username'] ?? '';
|
||||
if ($username === '') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
function loadPermsFor(string $u): array {
|
||||
try {
|
||||
if (function_exists('loadUserPermissions')) {
|
||||
$p = loadUserPermissions($u);
|
||||
return is_array($p) ? $p : [];
|
||||
}
|
||||
if (class_exists('userModel') && method_exists('userModel', 'getUserPermissions')) {
|
||||
$all = userModel::getUserPermissions();
|
||||
if (is_array($all)) {
|
||||
if (isset($all[$u])) return (array)$all[$u];
|
||||
$lk = strtolower($u);
|
||||
if (isset($all[$lk])) return (array)$all[$lk];
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {}
|
||||
return [];
|
||||
}
|
||||
|
||||
function isAdminUser(string $u, array $perms): bool {
|
||||
if (!empty($perms['admin']) || !empty($perms['isAdmin'])) return true;
|
||||
if (!empty($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true) return true;
|
||||
$role = $_SESSION['role'] ?? null;
|
||||
if ($role === 'admin' || $role === '1' || $role === 1) return true;
|
||||
if ($u) {
|
||||
$r = userModel::getUserRole($u);
|
||||
if ($r === '1') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function inUserFolderScope(string $folder, string $u, array $perms, bool $isAdmin): bool {
|
||||
if ($isAdmin) return true;
|
||||
$folderOnly = !empty($perms['folderOnly']) || !empty($perms['userFolderOnly']) || !empty($perms['UserFolderOnly']);
|
||||
if (!$folderOnly) return true;
|
||||
$f = trim($folder);
|
||||
if ($f === '' || strcasecmp($f, 'root') === 0) return false; // non-admin folderOnly: not root
|
||||
return ($f === $u) || (strpos($f, $u . '/') === 0);
|
||||
}
|
||||
|
||||
// --- inputs ---
|
||||
$folder = isset($_GET['folder']) ? trim((string)$_GET['folder']) : 'root';
|
||||
// validate folder path: allow "root" or nested segments matching REGEX_FOLDER_NAME
|
||||
if ($folder !== 'root') {
|
||||
$parts = array_filter(explode('/', trim($folder, "/\\ ")));
|
||||
if (empty($parts)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid folder name.']);
|
||||
exit;
|
||||
}
|
||||
foreach ($parts as $seg) {
|
||||
if (!preg_match(REGEX_FOLDER_NAME, $seg)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid folder name.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$folder = implode('/', $parts);
|
||||
}
|
||||
|
||||
$perms = loadPermsFor($username);
|
||||
$isAdmin = isAdminUser($username, $perms);
|
||||
|
||||
// base permissions via ACL
|
||||
$canRead = $isAdmin || ACL::canRead($username, $perms, $folder);
|
||||
$canWrite = $isAdmin || ACL::canWrite($username, $perms, $folder);
|
||||
$canShare = $isAdmin || ACL::canShare($username, $perms, $folder);
|
||||
|
||||
// scope + flags
|
||||
$inScope = inUserFolderScope($folder, $username, $perms, $isAdmin);
|
||||
$readOnly = !empty($perms['readOnly']);
|
||||
$disableUpload = !empty($perms['disableUpload']);
|
||||
|
||||
$canUpload = $canWrite && !$readOnly && !$disableUpload && $inScope;
|
||||
$canCreateFolder = $canWrite && !$readOnly && $inScope;
|
||||
$canRename = $canWrite && !$readOnly && $inScope;
|
||||
$canDelete = $canWrite && !$readOnly && $inScope;
|
||||
$canMoveIn = $canWrite && !$readOnly && $inScope;
|
||||
|
||||
// (optional) owner info if you need it client-side
|
||||
$owner = FolderModel::getOwnerFor($folder);
|
||||
|
||||
// output
|
||||
echo json_encode([
|
||||
'user' => $username,
|
||||
'folder' => $folder,
|
||||
'isAdmin' => $isAdmin,
|
||||
'flags' => [
|
||||
'folderOnly' => !empty($perms['folderOnly']) || !empty($perms['userFolderOnly']) || !empty($perms['UserFolderOnly']),
|
||||
'readOnly' => $readOnly,
|
||||
'disableUpload' => $disableUpload,
|
||||
],
|
||||
'owner' => $owner,
|
||||
'canView' => $canRead,
|
||||
'canUpload' => $canUpload,
|
||||
'canCreate' => $canCreateFolder,
|
||||
'canRename' => $canRename,
|
||||
'canDelete' => $canDelete,
|
||||
'canMoveIn' => $canMoveIn,
|
||||
'canShare' => $canShare,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
Reference in New Issue
Block a user