release(v1.9.12): feat(pro-acl): add user groups and group-aware ACL
This commit is contained in:
@@ -3,83 +3,26 @@
|
||||
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';
|
||||
require_once PROJECT_ROOT . '/src/controllers/AclAdminController.php';
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (empty($_SESSION['authenticated']) || empty($_SESSION['isAdmin'])) {
|
||||
http_response_code(401); echo json_encode(['error'=>'Unauthorized']); exit;
|
||||
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 = rtrim(META_DIR, "/\\") . DIRECTORY_SEPARATOR . '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::explicitAll($f); // legacy + granular
|
||||
|
||||
$isOwner = $has($rec['owners'], $user);
|
||||
$canViewAll = $isOwner || $has($rec['read'], $user);
|
||||
$canViewOwn = $has($rec['read_own'], $user);
|
||||
$canShare = $isOwner || $has($rec['share'], $user);
|
||||
$canUpload = $isOwner || $has($rec['write'], $user) || $has($rec['upload'], $user);
|
||||
|
||||
if ($canViewAll || $canViewOwn || $canUpload || $canShare || $isOwner
|
||||
|| $has($rec['create'],$user) || $has($rec['edit'],$user) || $has($rec['rename'],$user)
|
||||
|| $has($rec['copy'],$user) || $has($rec['move'],$user) || $has($rec['delete'],$user)
|
||||
|| $has($rec['extract'],$user) || $has($rec['share_file'],$user) || $has($rec['share_folder'],$user)) {
|
||||
$out[$f] = [
|
||||
'view' => $canViewAll,
|
||||
'viewOwn' => $canViewOwn,
|
||||
'write' => $has($rec['write'], $user) || $isOwner,
|
||||
'manage' => $isOwner,
|
||||
'share' => $canShare, // legacy
|
||||
'create' => $isOwner || $has($rec['create'], $user),
|
||||
'upload' => $isOwner || $has($rec['upload'], $user) || $has($rec['write'],$user),
|
||||
'edit' => $isOwner || $has($rec['edit'], $user) || $has($rec['write'],$user),
|
||||
'rename' => $isOwner || $has($rec['rename'], $user) || $has($rec['write'],$user),
|
||||
'copy' => $isOwner || $has($rec['copy'], $user) || $has($rec['write'],$user),
|
||||
'move' => $isOwner || $has($rec['move'], $user) || $has($rec['write'],$user),
|
||||
'delete' => $isOwner || $has($rec['delete'], $user) || $has($rec['write'],$user),
|
||||
'extract' => $isOwner || $has($rec['extract'], $user)|| $has($rec['write'],$user),
|
||||
'shareFile' => $isOwner || $has($rec['share_file'], $user) || $has($rec['share'],$user),
|
||||
'shareFolder' => $isOwner || $has($rec['share_folder'], $user) || $has($rec['share'],$user),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['grants' => $out], JSON_UNESCAPED_SLASHES);
|
||||
$ctrl = new AclAdminController();
|
||||
$grants = $ctrl->getUserGrants($user);
|
||||
echo json_encode(['grants' => $grants], JSON_UNESCAPED_SLASHES);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Failed to load grants', 'detail' => $e->getMessage()]);
|
||||
}
|
||||
@@ -3,12 +3,11 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../../../config/config.php';
|
||||
require_once PROJECT_ROOT . '/src/lib/ACL.php';
|
||||
require_once PROJECT_ROOT . '/src/controllers/AclAdminController.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']);
|
||||
@@ -24,98 +23,17 @@ if (empty($_SESSION['csrf_token']) || $csrf !== $_SESSION['csrf_token']) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---- Helpers ---------------------------------------------------------------
|
||||
function normalize_caps(array $row): array {
|
||||
// booleanize known keys
|
||||
$bool = function($v){ return !empty($v) && $v !== 'false' && $v !== 0; };
|
||||
$k = [
|
||||
'view','viewOwn','upload','manage','share',
|
||||
'create','edit','rename','copy','move','delete','extract',
|
||||
'shareFile','shareFolder','write'
|
||||
];
|
||||
$out = [];
|
||||
foreach ($k as $kk) $out[$kk] = $bool($row[$kk] ?? false);
|
||||
|
||||
// BUSINESS RULES:
|
||||
// A) Share Folder REQUIRES View (all). If shareFolder is true but view is false, force view=true.
|
||||
if ($out['shareFolder'] && !$out['view']) {
|
||||
$out['view'] = true;
|
||||
}
|
||||
|
||||
// B) Share File requires at least View (own). If neither view nor viewOwn set, set viewOwn=true.
|
||||
if ($out['shareFile'] && !$out['view'] && !$out['viewOwn']) {
|
||||
$out['viewOwn'] = true;
|
||||
}
|
||||
|
||||
// C) "write" does NOT imply view. It also does not imply granular here; ACL expands legacy write if present.
|
||||
return $out;
|
||||
}
|
||||
|
||||
function sanitize_grants_map(array $grants): array {
|
||||
$out = [];
|
||||
foreach ($grants as $folder => $caps) {
|
||||
if (!is_string($folder)) $folder = (string)$folder;
|
||||
if (!is_array($caps)) $caps = [];
|
||||
$out[$folder] = normalize_caps($caps);
|
||||
}
|
||||
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)) {
|
||||
|
||||
try {
|
||||
$ctrl = new AclAdminController();
|
||||
$res = $ctrl->saveUserGrantsPayload($in ?? []);
|
||||
echo json_encode($res, JSON_UNESCAPED_SLASHES);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
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}]}']);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Failed to save grants', 'detail' => $e->getMessage()]);
|
||||
}
|
||||
Reference in New Issue
Block a user