245 lines
9.5 KiB
PHP
245 lines
9.5 KiB
PHP
<?php
|
|
// public/api/folder/capabilities.php
|
|
|
|
/**
|
|
* @OA\Get(
|
|
* path="/api/folder/capabilities.php",
|
|
* summary="Get effective capabilities for the current user in a folder",
|
|
* description="Computes the caller's capabilities for a given folder by combining account flags (readOnly/disableUpload), ACL grants (read/write/share), and the user-folder-only scope. Returns booleans indicating what the user can do.",
|
|
* operationId="getFolderCapabilities",
|
|
* tags={"Folders"},
|
|
* security={{"cookieAuth": {}}},
|
|
*
|
|
* @OA\Parameter(
|
|
* name="folder",
|
|
* in="query",
|
|
* required=false,
|
|
* description="Target folder path. Defaults to 'root'. Supports nested paths like 'team/reports'.",
|
|
* @OA\Schema(type="string"),
|
|
* example="projects/acme"
|
|
* ),
|
|
*
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="Capabilities computed successfully.",
|
|
* @OA\JsonContent(
|
|
* type="object",
|
|
* required={"user","folder","isAdmin","flags","canView","canUpload","canCreate","canRename","canDelete","canMoveIn","canShare"},
|
|
* @OA\Property(property="user", type="string", example="alice"),
|
|
* @OA\Property(property="folder", type="string", example="projects/acme"),
|
|
* @OA\Property(property="isAdmin", type="boolean", example=false),
|
|
* @OA\Property(
|
|
* property="flags",
|
|
* type="object",
|
|
* required={"folderOnly","readOnly","disableUpload"},
|
|
* @OA\Property(property="folderOnly", type="boolean", example=false),
|
|
* @OA\Property(property="readOnly", type="boolean", example=false),
|
|
* @OA\Property(property="disableUpload", type="boolean", example=false)
|
|
* ),
|
|
* @OA\Property(property="owner", type="string", nullable=true, example="alice"),
|
|
* @OA\Property(property="canView", type="boolean", example=true, description="User can view items in this folder."),
|
|
* @OA\Property(property="canUpload", type="boolean", example=true, description="User can upload/edit/rename/move/delete items (i.e., WRITE)."),
|
|
* @OA\Property(property="canCreate", type="boolean", example=true, description="User can create subfolders here."),
|
|
* @OA\Property(property="canRename", type="boolean", example=true, description="User can rename items here."),
|
|
* @OA\Property(property="canDelete", type="boolean", example=true, description="User can delete items here."),
|
|
* @OA\Property(property="canMoveIn", type="boolean", example=true, description="User can move items into this folder."),
|
|
* @OA\Property(property="canShare", type="boolean", example=false, description="User can create share links for this folder.")
|
|
* )
|
|
* ),
|
|
* @OA\Response(response=400, description="Invalid folder name."),
|
|
* @OA\Response(response=401, ref="#/components/responses/Unauthorized")
|
|
* )
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
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 isOwnerOrAncestorOwner(string $user, array $perms, string $folder): bool {
|
|
$f = ACL::normalizeFolder($folder);
|
|
// direct owner
|
|
if (ACL::isOwner($user, $perms, $f)) return true;
|
|
// ancestor owner
|
|
while ($f !== '' && strcasecmp($f, 'root') !== 0) {
|
|
$pos = strrpos($f, '/');
|
|
if ($pos === false) break;
|
|
$f = substr($f, 0, $pos);
|
|
if ($f === '' || strcasecmp($f, 'root') === 0) break;
|
|
if (ACL::isOwner($user, $perms, $f)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* folder-only scope:
|
|
* - Admins: always in scope
|
|
* - Non folder-only accounts: always in scope
|
|
* - Folder-only accounts: in scope iff:
|
|
* - folder == username OR subpath of username, OR
|
|
* - user is owner of this folder (or any ancestor)
|
|
*/
|
|
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 = ACL::normalizeFolder($folder);
|
|
if ($f === 'root' || $f === '') {
|
|
// folder-only users cannot act on root unless they own a subfolder (handled below)
|
|
return isOwnerOrAncestorOwner($u, $perms, $f);
|
|
}
|
|
|
|
if ($f === $u || str_starts_with($f, $u . '/')) return true;
|
|
|
|
// Treat ownership as in-scope
|
|
return isOwnerOrAncestorOwner($u, $perms, $f);
|
|
}
|
|
|
|
// --- inputs ---
|
|
$folder = isset($_GET['folder']) ? trim((string)$_GET['folder']) : 'root';
|
|
|
|
// validate folder path
|
|
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);
|
|
}
|
|
|
|
// --- user + flags ---
|
|
$perms = loadPermsFor($username);
|
|
$isAdmin = ACL::isAdmin($perms);
|
|
$readOnly = !empty($perms['readOnly']);
|
|
$inScope = inUserFolderScope($folder, $username, $perms, $isAdmin);
|
|
|
|
// --- ACL base abilities ---
|
|
$canViewBase = $isAdmin || ACL::canRead($username, $perms, $folder);
|
|
$canViewOwn = $isAdmin || ACL::canReadOwn($username, $perms, $folder);
|
|
$canWriteBase = $isAdmin || ACL::canWrite($username, $perms, $folder);
|
|
$canShareBase = $isAdmin || ACL::canShare($username, $perms, $folder);
|
|
|
|
$canManageBase = $isAdmin || ACL::canManage($username, $perms, $folder);
|
|
|
|
// granular base
|
|
$gCreateBase = $isAdmin || ACL::canCreate($username, $perms, $folder);
|
|
$gRenameBase = $isAdmin || ACL::canRename($username, $perms, $folder);
|
|
$gDeleteBase = $isAdmin || ACL::canDelete($username, $perms, $folder);
|
|
$gMoveBase = $isAdmin || ACL::canMove($username, $perms, $folder);
|
|
$gUploadBase = $isAdmin || ACL::canUpload($username, $perms, $folder);
|
|
$gEditBase = $isAdmin || ACL::canEdit($username, $perms, $folder);
|
|
$gCopyBase = $isAdmin || ACL::canCopy($username, $perms, $folder);
|
|
$gExtractBase = $isAdmin || ACL::canExtract($username, $perms, $folder);
|
|
$gShareFile = $isAdmin || ACL::canShareFile($username, $perms, $folder);
|
|
$gShareFolder = $isAdmin || ACL::canShareFolder($username, $perms, $folder);
|
|
|
|
// --- Apply scope + flags to effective UI actions ---
|
|
$canView = $canViewBase && $inScope; // keep scope for folder-only
|
|
$canUpload = $gUploadBase && !$readOnly && $inScope;
|
|
$canCreate = $canManageBase && !$readOnly && $inScope; // Create **folder**
|
|
$canRename = $canManageBase && !$readOnly && $inScope; // Rename **folder**
|
|
$canDelete = $gDeleteBase && !$readOnly && $inScope;
|
|
// Destination can receive items if user can create/write (or manage) here
|
|
$canReceive = ($gUploadBase || $gCreateBase || $canManageBase) && !$readOnly && $inScope;
|
|
// Back-compat: expose as canMoveIn (used by toolbar/context-menu/drag&drop)
|
|
$canMoveIn = $canReceive;
|
|
$canMoveAlias = $canMoveIn;
|
|
$canEdit = $gEditBase && !$readOnly && $inScope;
|
|
$canCopy = $gCopyBase && !$readOnly && $inScope;
|
|
$canExtract = $gExtractBase && !$readOnly && $inScope;
|
|
|
|
// Sharing respects scope; optionally also gate on readOnly
|
|
$canShare = $canShareBase && $inScope; // legacy umbrella
|
|
$canShareFileEff = $gShareFile && $inScope;
|
|
$canShareFoldEff = $gShareFolder && $inScope;
|
|
|
|
// never allow destructive ops on root
|
|
$isRoot = ($folder === 'root');
|
|
if ($isRoot) {
|
|
$canRename = false;
|
|
$canDelete = false;
|
|
$canShareFoldEff = false;
|
|
$canMoveFolder = false;
|
|
}
|
|
|
|
if (!$isRoot) {
|
|
$canMoveFolder = (ACL::canManage($username, $perms, $folder) || ACL::isOwner($username, $perms, $folder))
|
|
&& !$readOnly;
|
|
}
|
|
|
|
$owner = null;
|
|
try { $owner = FolderModel::getOwnerFor($folder); } catch (Throwable $e) {}
|
|
|
|
echo json_encode([
|
|
'user' => $username,
|
|
'folder' => $folder,
|
|
'isAdmin' => $isAdmin,
|
|
'flags' => [
|
|
//'folderOnly' => !empty($perms['folderOnly']) || !empty($perms['userFolderOnly']) || !empty($perms['UserFolderOnly']),
|
|
'readOnly' => $readOnly,
|
|
],
|
|
'owner' => $owner,
|
|
|
|
// viewing
|
|
'canView' => $canView,
|
|
'canViewOwn' => $canViewOwn,
|
|
|
|
// write-ish
|
|
'canUpload' => $canUpload,
|
|
'canCreate' => $canCreate,
|
|
'canRename' => $canRename,
|
|
'canDelete' => $canDelete,
|
|
'canMoveIn' => $canMoveIn,
|
|
'canMove' => $canMoveAlias,
|
|
'canMoveFolder'=> $canMoveFolder,
|
|
'canEdit' => $canEdit,
|
|
'canCopy' => $canCopy,
|
|
'canExtract' => $canExtract,
|
|
|
|
// sharing
|
|
'canShare' => $canShare, // legacy
|
|
'canShareFile' => $canShareFileEff,
|
|
'canShareFolder' => $canShareFoldEff,
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); |