{"record":{"id":"e4af553dcd5ccee0","repo":"passbolt/passbolt_api","slug":"could-not-validate-password-data-e4af55","errorCode":null,"errorMessage":"Could not validate password data.","messagePattern":"Could not validate password data\\.","errorType":"validation","errorClass":"App\\Error\\Exception\\CustomValidationException","httpStatus":null,"severity":"error","filePath":"plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php","lineNumber":277,"sourceCode":"     * Ensure the correct number of passwords are provided by the end user data\n     *\n     * Should be done in the same transaction than the save for data integrity purpose\n     * This assume the account_recovery_private_key_passwords is therefore validated\n     * It uses the original data for array operation speed versus working with entities\n     *\n     * @CustomValidationException if some of the private key passwords are missing\n     * @return void\n     */\n    private function assertPasswordsCount(): void\n    {\n        $passwordsData = $this->getData('account_recovery_private_key_passwords');\n\n        // Check there is the correct number of passwords\n        $actual = count($passwordsData);\n        $expected = $this->AccountRecoveryPrivateKeyPasswords->find()->all()->count();\n        if ($actual !== $expected) {\n            $msg = __('An invalid number of passwords sent. Expected {0} and got {1}.', $expected, $actual);\n            throw new CustomValidationException(__('Could not validate password data.'), [\n                'account_recovery_private_key_passwords' => [\n                    'invalidPasswordCount' => $msg,\n                ],\n            ]);\n        }\n\n        // Check there is the correct private key id for the passwords\n        $missing = $this->AccountRecoveryPrivateKeys->find()\n            ->select('id')\n            ->where(['id NOT IN' => Hash::extract($passwordsData, '{n}.private_key_id')])\n            ->all();\n        if (count($missing)) {\n            throw new CustomValidationException(__('Could not validate password data.'), [\n                'account_recovery_private_key_passwords' => [\n                    'missingPasswordForPrivateKeyIds' => Hash::extract($missing->toArray(), '{n}.id'),\n                ],\n            ]);\n        }","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php#L259-L295","documentation":"Thrown by AccountRecoveryOrganizationPolicySetService::assertPasswordsCount when updating the organization account-recovery policy: the number of account_recovery_private_key_passwords entries sent in the request does not equal the number of password records already stored for the organization's recovery private key. The service wraps the mismatch in a CustomValidationException so the client receives a field-level error on 'account_recovery_private_key_passwords' with an invalidPasswordCount message naming expected vs actual counts.","triggerScenarios":"POST/PUT to the account-recovery organization policy settings endpoint with an 'account_recovery_private_key_passwords' array whose length differs from count(AccountRecoveryPrivateKeyPasswords) — e.g. omitting the passwords array entirely, sending only a subset of shares, or sending duplicates while rotating the policy.","commonSituations":"Client SDK or script not including the private-key passwords block during policy setup/rotation; stale client state after the organization key was re-generated so the client sends old counts; partially failed previous submission left an unexpected number of rows in account_recovery_private_key_passwords.","solutions":["Count the existing rows first (SELECT COUNT(*) FROM account_recovery_private_key_passwords) and send exactly that many password entries","Fix the client payload so every stored private key has exactly one corresponding password entry (one per share recipient)","If the stored rows are stale from a failed rotation, regenerate the organization recovery key and restart the policy setup flow","Read the invalidPasswordCount message in the error details — it states the expected and actual numbers; align the payload to it"],"exampleFix":"// before\nPUT /account-recovery/organization-policies\n{\"policy\": \"mandatory\", \"account_recovery_private_key_passwords\": [/* only 1 entry */]}\n// after\nPUT /account-recovery/organization-policies\n{\"policy\": \"mandatory\", \"account_recovery_private_key_passwords\": [/* one entry per stored key share, count matches DB */]}","handlingStrategy":"validation","validationCode":"// Before submitting the policy update:\n$expected = $this->AccountRecoveryPrivateKeyPasswords->find()->all()->count();\nif (count($payload['account_recovery_private_key_passwords'] ?? []) !== $expected) {\n    throw new \\InvalidArgumentException(\"Send exactly {$expected} password entries\");\n}","typeGuard":"// Guard payload shape before the API call\n$ok = is_array($payload['account_recovery_private_key_passwords'] ?? null)\n    && count($payload['account_recovery_private_key_passwords']) === $expectedCount;","tryCatchPattern":"try {\n    $service->updatePolicy($uac, $data);\n} catch (\\App\\Error\\Exception\\CustomValidationException $e) {\n    $details = $e->getErrors()['account_recovery_private_key_passwords'] ?? [];\n    if (isset($details['invalidPasswordCount'])) {\n        // parse expected/actual from $details['invalidPasswordCount'] and rebuild payload\n    }\n}","preventionTips":["Always fetch the current organization policy and key first, then build a one-password-per-key-share payload","After any key rotation, discard cached client payloads and rebuild from server state","Parse the invalidPasswordCount detail message — it gives exact expected and actual counts","Test policy updates against a staging instance before production"],"tags":["validation","account-recovery","payload-count-mismatch"],"backgroundTag":"schema-validation-failed","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"}