{"record":{"id":"b7c1c16db9cbfe5c","repo":"Leantime/leantime","slug":"32001","errorCode":"-32001","errorMessage":"You are not allowed to re-sort one or more of these projects.","messagePattern":"You are not allowed to re-sort one or more of these projects\\.","errorType":"exception","errorClass":"Leantime\\Core\\Exceptions\\AuthorizationException","httpStatus":403,"severity":"error","filePath":"app/Domain/Projects/Services/Projects.php","lineNumber":2745,"sourceCode":"     * @throws AuthorizationException If the caller cannot manage any project in the batch\n     *\n     * @api\n     */\n    public function patchProjectStatusAndSorting(array $params, ?string $handler = null): bool\n    {\n        foreach ($params as $status => $projectList) {\n            if (! is_numeric($status) || empty($projectList)) {\n                continue;\n            }\n\n            foreach (explode('&', $projectList) as $projectString) {\n                // jQuery sortable serializes ids as \"item[]=ID\" (strip the 7-char prefix).\n                $projectId = (int) substr($projectString, 7);\n                if ($projectId <= 0) {\n                    continue;\n                }\n                if (! $this->userCanManageProject($projectId)) {\n                    throw new AuthorizationException('You are not allowed to re-sort one or more of these projects.');\n                }\n            }\n        }\n\n        return $this->updateProjectStatusAndSorting($params, $handler);\n    }\n\n    /**\n     * Authorized JSON-RPC entry point for Program Timeline (Gantt) re-sorting.\n     *\n     * Validates manager+ access for every entity in the mixed payload (pgm-/ticket-\n     * prefixed and legacy numeric ids) before delegating to updateProjectSorting().\n     * Ticket ids are resolved to their project so the same manage-access rule applies.\n     *\n     * @param  array  $params  Map of (pgm-{id}|ticket-{id}|{id}) => sort position\n     * @return bool True on success (false only if the underlying write fails)\n     *\n     * @throws NotFoundException If a ticket-{id} key references a ticket that does not exist","sourceCodeStart":2727,"sourceCodeEnd":2763,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Projects/Services/Projects.php#L2727-L2763","documentation":"Projects::patchProjectStatusAndSorting() is the authorized JSON-RPC entry point for the project kanban status+sort update. It parses params shaped as {status => jQuery-serialized string like \"item[]=3&item[]=7\"} (ids extracted by stripping the 7-char prefix), and for every project id requires userCanManageProject(): role of manager or higher AND assignment to that project (admins/owners bypass). Any failing id throws AuthorizationException, mapped to JSON-RPC -32001, before any write happens.","triggerScenarios":"A user below manager dragging project cards between status columns; a manager re-sorting a project they are not assigned to; a payload that still contains project ids the caller lost access to after a role/assignment change.","commonSituations":"Drag-drop boards left open in a stale tab after the user's role was downgraded or assignment removed; organizations where managers manage only a subset of projects but the board lists all of them; scripts replaying a captured sort payload with a different account.","solutions":["Ensure the acting user has manager+ role and is assigned to every project in the batch (admin/owner bypasses the assignment check)","Hide drag-drop sorting UI for non-managers so the invalid call is never made","Catch AuthorizationException (-32001), then verify assignments with Projects::isUserAssignedToProject and retry with only the projects the user can manage"],"exampleFix":"// before\n$result = $projectsService->patchProjectStatusAndSorting($params);\n\n// after\nforeach ($params as $status => $list) {\n    foreach (explode('&', $list) as $item) {\n        $pid = (int) substr($item, 7);\n        if ($pid > 0 && ! $projectsService->userCanManageProject($pid)) {\n            unset($params[$status]); // or abort with a clear message\n            continue 2;\n        }\n    }\n}\n$result = $projectsService->patchProjectStatusAndSorting($params);","handlingStrategy":"validation","validationCode":"foreach ($params as $status => $list) {\n    foreach (explode('&', (string) $list) as $item) {\n        $pid = (int) substr($item, 7);\n        if ($pid > 0 && ! $projectsService->userCanManageProject($pid)) {\n            return ['error' => \"No manage rights on project {$pid}\"]; // fail before the call\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    $ok = $projectsService->patchProjectStatusAndSorting($params, $handler);\n} catch (\\Leantime\\Core\\Exceptions\\AuthorizationException $e) {\n    // -32001: role or assignment missing — surface a permissions message, do not retry blindly\n    $notify->error($e->getMessage());\n}","preventionTips":["Only enable drag-drop project sorting for manager+ users assigned to the visible projects","Call the public Projects::userCanManageProject() pre-check for each id in the serialized payload","Remember the payload format is jQuery-serialized 'item[]=ID' strings — parse with the same substr($s, 7) rule when validating"],"tags":["projects","authorization","kanban","sorting","json-rpc"],"backgroundTag":"insufficient-permissions","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}