{"record":{"id":"64dd94131aab01e1","repo":"passbolt/passbolt_api","slug":"could-not-validate-public-key-data-64dd94","errorCode":null,"errorMessage":"Could not validate public key data.","messagePattern":"Could not validate public key data\\.","errorType":"validation","errorClass":"App\\Error\\Exception\\CustomValidationException","httpStatus":null,"severity":"error","filePath":"plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php","lineNumber":118,"sourceCode":"        if ($isNewKeyProvided && $isRevokedKeyProvided) {\n            // assert old and new key$newKey\n            $newKey = $this->buildPublicKeyEntityFromDataOrFail($uac);\n            $oldKey = $this->buildRevokedKeyEntityFromDataOrFail($uac);\n\n            // If some existing backups are present\n            // assert new backups are provided\n            if ($this->backupsExists()) {\n                if (!$isPrivateKeyPasswordsProvided) {\n                    throw new BadRequestException(__('Invalid request. Passwords are required for this change.'));\n                }\n                // assert passwords backups format and numbers\n                $passwords = $this->buildPasswordEntitiesFromDataOrFail($uac, $newKey);\n            }\n            $newPolicy->account_recovery_organization_public_key = $newKey;\n        } else {\n            // If key is not changing reuse the old one\n            if (!isset($newPolicy->public_key_id)) {\n                throw new CustomValidationException(__('Could not validate public key data.'), [\n                    'public_key_id' => [\n                        '_required' => __('An organization public key is required.'),\n                    ],\n                ]);\n            } else {\n                if ($newPolicy->public_key_id !== $this->getCurrentPolicyEntity()->public_key_id) {\n                    throw new CustomValidationException(__('Could not validate public key data.'), [\n                        'public_key_id' => [\n                            'notCurrentPublicKeyId' => __('The public_key_id must match current policy public_key_id.'),\n                        ],\n                    ]);\n                }\n            }\n        }\n\n        // save new key and disable previous key and backups if any\n        return $this->updatePolicy($uac, $newPolicy, $oldKey, $newKey, $passwords);\n    }","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php#L100-L136","documentation":"This CustomValidationException is raised during an enabled-to-enabled policy change without key rotation: the request omitted 'public_key_id', so the service cannot determine which existing organization public key to reuse. The message 'Could not validate public key data.' carries a nested validation error (_required) indicating the organization public key identifier is required.","triggerScenarios":"Policy-only change (no new key, no revoked key) where the payload lacks both key rotation fields AND 'public_key_id'. Raised in the else branch of AccountRecoveryOrganizationPolicySetService::set() at line 118 when !isset($newPolicy->public_key_id).","commonSituations":"An admin changes the policy (e.g. mandatory to opt-in) with a minimal payload that omits the current public_key_id; a client library that only sends the policy field on updates; older client versions built before the public_key_id requirement; hand-crafted curl requests copied from enable examples.","solutions":["Add the current organization public key's id as 'public_key_id' in the request payload so the existing key is reused.","Fetch the current policy via GET /account-recovery/organization-policies.json to obtain the correct public_key_id before submitting.","If a key rotation was intended instead, provide both the new key and the revoked key rather than relying on public_key_id.","Update the client library/UI to always include public_key_id on non-rotation policy updates."],"exampleFix":"// before (policy change without key reference)\nawait passbolt.setAccountRecoveryOrganizationPolicy({\n  policy: 'opt-in'\n});\n\n// after (include current public_key_id to reuse existing key)\nawait passbolt.setAccountRecoveryOrganizationPolicy({\n  policy: 'opt-in',\n  public_key_id: currentPolicy.account_recovery_organization_public_key.id\n});","handlingStrategy":"validation","validationCode":"if (!payload.account_recovery_organization_public_key && !payload.account_recovery_organization_revoked_key && !payload.public_key_id) {\n  throw new Error('Policy-only change requires public_key_id of the current organization key.');\n}","typeGuard":"function canReuseExistingKey(p) {\n  return typeof p.public_key_id === 'string' && p.public_key_id.length > 0;\n}","tryCatchPattern":"try {\n  await passbolt.setAccountRecoveryOrganizationPolicy(payload);\n} catch (e) {\n  if (e.status === 400 && /Could not validate public key data/.test(e.body ?? e.message) && e.body?.public_key_id?._required) {\n    const current = await passbolt.getAccountRecoveryOrganizationPolicy();\n    payload.public_key_id = current.account_recovery_organization_public_key.id;\n    return retry(payload);\n  }\n  throw e;\n}","preventionTips":["Always fetch the current policy before a non-rotation update and carry public_key_id through.","Include public_key_id in any client model representing the organization policy settings form.","Distinguish clearly in code between rotation requests and reuse-key requests."],"tags":["api","validation","account-recovery","missing-field"],"backgroundTag":"missing-required-argument","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"}