{"record":{"id":"b909921fdcb04417","repo":"Leantime/leantime","slug":"32001-b90992","errorCode":"-32001","errorMessage":"You are not allowed to re-sort tasks.","messagePattern":"You are not allowed to re-sort tasks\\.","errorType":"exception","errorClass":"Leantime\\Core\\Exceptions\\AuthorizationException","httpStatus":403,"severity":"error","filePath":"app/Domain/Tickets/Services/Tickets.php","lineNumber":3443,"sourceCode":"    /**\n     * Authorized JSON-RPC entry point for Gantt re-sorting of tickets/milestones.\n     *\n     * Enforces editor+ and per-ticket project access (the RPC endpoint has no\n     * controller-level gate), then delegates to the internal updateTicketSorting().\n     *\n     * @param  array  $params  Array of ticketId => sortPosition from Gantt drag-drop\n     * @return bool True on success (false only if the underlying write fails)\n     *\n     * @throws AuthorizationException If the caller is not an editor, or is not assigned to a referenced task's project\n     * @throws NotFoundException If a referenced task does not exist\n     *\n     * @api\n     */\n    #[RequiresPermission(TicketsPermissions::EDIT, entityScoped: true)]\n    public function sortTickets(array $params): bool\n    {\n        if (! Auth::userIsAtLeast(Roles::$editor)) {\n            throw new AuthorizationException('You are not allowed to re-sort tasks.');\n        }\n\n        $userId = session('userdata.id');\n        foreach (array_keys($params) as $ticketId) {\n            $ticket = $this->getTicket((int) $ticketId);\n            if (! $ticket) {\n                throw new NotFoundException('A task referenced in the sort order could not be found.');\n            }\n            if (! $this->projectService->isUserAssignedToProject($userId, $ticket->projectId)) {\n                throw new AuthorizationException('You are not allowed to re-sort this task.');\n            }\n        }\n\n        return $this->updateTicketSorting($params);\n    }\n\n    /**\n     * Update ticket sorting with hierarchical cascade for milestone children","sourceCodeStart":3425,"sourceCodeEnd":3461,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Tickets/Services/Tickets.php#L3425-L3461","documentation":"Tickets::sortTickets() (Gantt/kanban drag-drop re-sorting) first checks the SESSION-wide role with Auth::userIsAtLeast(Roles::$editor); anyone below editor (commenter, viewer) gets AuthorizationException (JSON-RPC -32001) regardless of project membership. Note the distinction: this first gate looks at the user's global session role, while the subsequent per-ticket loop checks project-scoped access — a user can pass this gate and still fail the later assignment check.","triggerScenarios":"A commenter or viewer account dragging tasks to re-sort; an API key whose underlying service-account user has a low role; a session carrying a stale role after the user's role was downgraded (session data predates the change).","commonSituations":"Drag-drop sort enabled in the UI for all roles; automations using a commenter-level API key; role downgrades that only take effect after re-login because the session still carries the old role.","solutions":["Perform sort calls with an editor+ (or higher) account","Raise the user's role if they legitimately need to re-sort tasks","Hide/disable drag-drop re-sorting for non-editor roles in the UI, and catch AuthorizationException (-32001) to show a clear message"],"exampleFix":"// before\n$ticketsService->sortTickets($params); // fails for commenter role\n\n// after\nif (! \\Leantime\\Domain\\Auth\\Services\\Auth::userIsAtLeast(\\Leantime\\Domain\\Auth\\Models\\Roles::$editor)) {\n    return 'Sorting requires an editor role';\n}\n$ticketsService->sortTickets($params);","handlingStrategy":"validation","validationCode":"use Leantime\\Domain\\Auth\\Models\\Roles;\nuse Leantime\\Domain\\Auth\\Services\\Auth;\n\nif (! Auth::userIsAtLeast(Roles::$editor)) {\n    return ['error' => 'Sorting tasks requires the editor role'];\n}\n$ok = $ticketsService->sortTickets($params);","typeGuard":null,"tryCatchPattern":"try {\n    $ok = $ticketsService->sortTickets($params);\n} catch (\\Leantime\\Core\\Exceptions\\AuthorizationException $e) {\n    // -32001: session role below editor — disable drag-drop for this user\n    $ui->disableSorting();\n}","preventionTips":["Enable drag-drop sorting only for editor+ users","Check the session role with Auth::userIsAtLeast(Roles::$editor) before issuing sort calls","Remember this gate is on the GLOBAL session role — re-login after role changes"],"tags":["tickets","authorization","roles","sorting","json-rpc"],"backgroundTag":"insufficient-permissions","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}