{"record":{"id":"6ee70defccb0ae52","repo":"passbolt/passbolt_api","slug":"invalid-request-no-policy-change","errorCode":null,"errorMessage":"Invalid request. No policy change.","messagePattern":"Invalid request\\. No policy change\\.","errorType":"http","errorClass":"Cake\\Http\\Exception\\BadRequestException","httpStatus":400,"severity":"warning","filePath":"plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php","lineNumber":53,"sourceCode":"     * @param array $data user provided data\n     * @return \\Passbolt\\AccountRecovery\\Model\\Entity\\AccountRecoveryOrganizationPolicy\n     */\n    public function set(UserAccessControl $uac, array $data): AccountRecoveryOrganizationPolicy\n    {\n        $this->setData($data);\n\n        // assert policy is provided as it should in any case\n        $newPolicy = $this->buildAndValidatePolicyEntityFromData($uac);\n\n        // Check request composition to understand user goal\n        $isPolicyChange = $this->isPolicyChange();\n        $isNewKeyProvided = $this->isPublicKeyProvided();\n        $isRevokedKeyProvided = $this->isRevokedKeyProvided();\n        $isPrivateKeyPasswordsProvided = $this->isPrivateKeyPasswordsProvided();\n\n        // if policy has not changed and (new key not provided or revoked key not provided)\n        if (!$isPolicyChange && !$isNewKeyProvided && !$isRevokedKeyProvided) {\n            throw new BadRequestException(__('Invalid request. No policy change.'));\n        }\n        if (!$isPolicyChange && $isNewKeyProvided && !$isRevokedKeyProvided) {\n            throw new BadRequestException(__('Invalid request. Revoked key is required for key rotation.'));\n        }\n        /** @psalm-suppress RedundantCondition */\n        if (!$isPolicyChange && !$isNewKeyProvided && $isRevokedKeyProvided) {\n            throw new BadRequestException(__('Invalid request. New key is required for key rotation.'));\n        }\n\n        // if disabled => enabled\n        if ($this->isEnabling()) {\n            // if public key is not provided\n            if (!$isNewKeyProvided) {\n                throw new BadRequestException(__('Invalid request. An organization recovery public key is required.'));\n            }\n            // if key revocation or passwords provided\n            if ($isRevokedKeyProvided || $isPrivateKeyPasswordsProvided) {\n                throw new BadRequestException(__('Invalid request. Revoked key or passwords are not required.'));","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php#L35-L71","documentation":"Thrown by `set` when the request is a no-op: the policy value does not change AND neither a new public key nor a revoked key is provided. Passbolt rejects requests that would leave the organization recovery configuration untouched because there is nothing to persist.","triggerScenarios":"PUT /account-recovery/organization-settings.json with the same policy value currently stored and no `armored_key`/`new_key`/`revoked_key` fields in the payload.","commonSituations":"Frontend submits the settings form without changes; automation re-applies the same desired state; client omitted the new-key field by mistake while intending a rotation.","solutions":["Only call the endpoint when the policy actually changes or a key rotation/revocation is intended","Include a new public key or a revoked key if you intend to rotate keys while keeping the same policy","Check the current policy via GET first and skip the update if identical"],"exampleFix":"// before\nawait api.put('/account-recovery/organization-settings.json', { policy: 'opt-in' }); // already opt-in\n// after\nif (currentPolicy !== 'opt-in') {\n  await api.put('/account-recovery/organization-settings.json', { policy: 'opt-in' });\n}","handlingStrategy":"validation","validationCode":"if (currentPolicy === desiredPolicy && !newKey && !revokedKey) return; // skip no-op update","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Skip submissions when nothing changed","Diff desired vs current settings before PUT","Track whether the user actually modified the form"],"tags":["account-recovery","no-op","bad-request","validation"],"backgroundTag":"invalid-argument-value","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"}