{"record":{"id":"cd8b9b1fb425aede","repo":"getgrav/grav","slug":"403-cd8b9b","errorCode":"403","errorMessage":"Forbidden","messagePattern":"Forbidden","errorType":"http","errorClass":"RuntimeException","httpStatus":403,"severity":"error","filePath":"system/src/Grav/Framework/Flex/FlexObject.php","lineNumber":738,"sourceCode":"    /**\n     * @param string|null $key\n     * @return FlexObject|FlexObjectInterface\n     */\n    public function createCopy(?string $key = null)\n    {\n        $this->markAsCopy();\n\n        return $this->create($key);\n    }\n\n    /**\n     * @param UserInterface|null $user\n     */\n    public function check(?UserInterface $user = null): void\n    {\n        // If user has been provided, check if the user has permissions to save this object.\n        if ($user && !$this->isAuthorized('save', null, $user)) {\n            throw new \\RuntimeException('Forbidden', 403);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     * @see FlexObjectInterface::save()\n     */\n    public function save()\n    {\n        $this->triggerEvent('onBeforeSave');\n\n        $storage = $this->getFlexDirectory()->getStorage();\n\n        $storageKey = $this->getStorageKey() ?:  '@@' . spl_object_hash($this);\n\n        $result = $storage->replaceRows([$storageKey => $this->prepareStorage()]);\n\n        if (method_exists($this, 'clearMediaCache')) {","sourceCodeStart":720,"sourceCodeEnd":756,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Flex/FlexObject.php#L720-L756","documentation":"FlexObject::check(?UserInterface $user) (line 738) is the pre-save authorization gate: when a user is supplied and isAuthorized('save', null, $user) returns false, it throws RuntimeException('Forbidden', 403). Authorization comes from the flex type's configured ACL rules combined with the acting user's access, so this error is about permissions, not about the object's data.","triggerScenarios":"Calling $object->check($currentUser) before save/delete when the user lacks save permission on that flex type; flex REST/API endpoints invoked by unauthenticated clients or users whose access rules deny the type; admin controllers delegating validation to check().","commonSituations":"API requests with missing/expired credentials so the user resolves to anonymous; new ACL rules for a flex type that forget to grant save to the editing role; front-end forms letting non-privileged users submit flex objects.","solutions":["Grant the acting user save access on the flex type via user/group access rules or the directory's authorization configuration.","Verify authentication before calling check(): if the request is anonymous, respond 401/login instead of invoking the gate.","Use the non-throwing twin $object->isAuthorized('save', null, $user) for control flow, or catch RuntimeException with code 403 and surface an HTTP 403 naming the flex type."],"exampleFix":"// before\n$object->check($user); // throws RuntimeException('Forbidden', 403)\n$object->save();\n\n// after\nif (!$object->isAuthorized('save', null, $user)) {\n    return $response->withStatus(403);\n}\n$object->save();","handlingStrategy":"validation","validationCode":"if (null === $user || $user->authenticated === false) {\n    // return 401/login instead of invoking check()\n}\nif (!$object->isAuthorized('save', null, $user)) {\n    // return 403 before calling check()/save()\n}","typeGuard":"function canSaveFlexObject(\\Grav\\Framework\\Flex\\FlexObject $object, ?\\Grav\\Common\\User\\Interfaces\\UserInterface $user): bool\n{\n    return null === $user || $object->isAuthorized('save', null, $user);\n}","tryCatchPattern":"try {\n    $object->check($user);\n} catch (\\RuntimeException $e) {\n    if (403 === $e->getCode()) {\n        // map to HTTP 403, include the flex type in the error payload\n    }\n    throw $e;\n}","preventionTips":["Authenticate first: anonymous requests should never reach check().","Pre-check isAuthorized('save', ...) when you need control flow instead of exceptions.","Test ACL rules for each flex type after changing access configuration."],"tags":["flex","authorization","permissions","http-403","acl"],"backgroundTag":"permission-denied-403","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}