{"record":{"id":"35eeb6fa87f9f786","repo":"passbolt/passbolt_api","slug":"this-operation-is-not-allowed-the-current-page-does-not","errorCode":null,"errorMessage":"This operation is not allowed. The current page does not match the total number of pages.","messagePattern":"This operation is not allowed\\. The current page does not match the total number of pages\\.","errorType":"http","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"plugins/PassboltCe/Mobile/src/Service/Transfers/TransfersUpdateService.php","lineNumber":144,"sourceCode":"                || $updated->status === Transfer::TRANSFER_STATUS_COMPLETE\n                || $updated->status === Transfer::TRANSFER_STATUS_ERROR)\n            &&\n            ($original->status === Transfer::TRANSFER_STATUS_CANCEL\n                || $original->status === Transfer::TRANSFER_STATUS_COMPLETE)\n        ) {\n            $msg = __('This operation is not allowed.') . ' ';\n            $msg .= __('The operation is already over.');\n            throw new ForbiddenException($msg);\n        }\n\n        // Cannot \"complete\" without being on last page\n        if (\n            $updated->status === Transfer::TRANSFER_STATUS_COMPLETE &&\n            $updated->current_page !== $original->total_pages - 1\n        ) {\n            $msg = __('This operation is not allowed.') . ' ';\n            $msg .= __('The current page does not match the total number of pages.');\n            throw new ForbiddenException($msg);\n        }\n    }\n\n    /**\n     * Check if operation is allowed\n     *\n     * @param \\Passbolt\\Mobile\\Model\\Entity\\Transfer $transfer entity\n     * @param \\App\\Utility\\UserAccessControl $uac user access control object\n     * @throws \\Cake\\Http\\Exception\\ForbiddenException if operation is not allowed for example:\n     * - Transfer or AuthToken is for another user\n     * - Authentication token is expired\n     * @return void\n     */\n    private function assertOperationIsAllowed(Transfer $transfer, UserAccessControl $uac): void\n    {\n        if ($transfer->user_id !== $uac->getId()) {\n            throw new ForbiddenException(__('This operation is not allowed for this user.'));\n        }","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/Mobile/src/Service/Transfers/TransfersUpdateService.php#L126-L162","documentation":"Thrown by TransfersUpdateService::assertTransitionAllowed when a mobile transfer is updated to status COMPLETE while the current_page field does not equal total_pages - 1. Passbolt requires every page of the transfer payload to be uploaded before the transfer can be marked complete, so completing early is rejected as a forbidden state transition.","triggerScenarios":"Calling PUT/PUT JSON on /transfers/{id}.json with body {\"status\":\"complete\"} (via TransfersUpdateService::update) when current_page != total_pages - 1 — e.g. completing after uploading fewer pages than the transfer declared, or uploading the last page to the wrong page index.","commonSituations":"Mobile app bugs in page-index bookkeeping (0-based pages vs 1-based UI counters); client retrying the final page upload after an off-by-one; a transfer created with total_pages larger than the data actually chunked into.","solutions":["Ensure the client uploads all pages and sets current_page = total_pages - 1 in the same request that sets status to complete","Check the client's page counter is 0-based to match total_pages - 1","If the payload shrank, start a new transfer with the correct total_pages instead of forcing complete on this one"],"exampleFix":"// before\nawait updateTransfer(id, { status: 'complete', current_page: 3 }); // total_pages: 5\n// after\nawait updateTransfer(id, { status: 'complete', current_page: 4 }); // total_pages - 1","handlingStrategy":"validation","validationCode":"const totalPages = transfer.totalPages;\nconst currentPage = transfer.currentPage;\nif (status === 'complete' && currentPage !== totalPages - 1) {\n  throw new Error(`Upload remaining pages: ${totalPages - 1 - currentPage} left`);\n}\nawait updateTransfer(id, { status, current_page: currentPage });","typeGuard":null,"tryCatchPattern":"try {\n  await api.updateTransfer(id, { status: 'complete', current_page: lastPageIndex });\n} catch (e) {\n  if (e.code === 403 && /does not match the total number of pages/.test(e.message)) {\n    // resume uploading remaining pages, then retry\n  }\n}","preventionTips":["Track page indices 0-based to match total_pages - 1","Only send status=complete together with the final page upload","Assert currentPage === totalPages - 1 client-side before completing"],"tags":["mobile","transfer","forbidden","state-transition"],"backgroundTag":"invalid-state-transition","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}