{"record":{"id":"e385d1d94af15ca9","repo":"passbolt/passbolt_api","slug":"the-metadata-key-is-already-shared-with-the-user","errorCode":null,"errorMessage":"The metadata key is already shared with the user.","messagePattern":"The metadata key is already shared with the user\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltCe/Metadata/src/Service/MetadataPrivateKeysCreateService.php","lineNumber":156,"sourceCode":"        } catch (RecordNotFoundException $exception) {\n            throw new NotFoundException(__('The metadata key does not exist or has been deleted.'));\n        }\n\n        // Assert private key does not already exist for the user/server\n        /** @var \\Passbolt\\Metadata\\Model\\Table\\MetadataPrivateKeysTable $metadataPrivateKeysTable */\n        $metadataPrivateKeysTable = $this->fetchTable('Passbolt/Metadata.MetadataPrivateKeys');\n        $metadataPrivateKey = $metadataPrivateKeysTable->find()\n            ->where(['metadata_key_id' => $metadataKeyId])\n            ->where(function (QueryExpression $exp) use ($data) {\n                if (isset($data['user_id'])) {\n                    return $exp->eq('user_id', $data['user_id']);\n                }\n\n                return $exp->isNull('user_id');\n            })\n            ->first();\n        if (!empty($metadataPrivateKey)) {\n            throw new BadRequestException(__('The metadata key is already shared with the user.'));\n        }\n    }\n\n    /**\n     * @param \\App\\Utility\\UserAccessControl $uac User access control.\n     * @param \\Passbolt\\Metadata\\Model\\Dto\\MetadataPrivateKeysCreateManyDto $dto User provided data.\n     * @return void\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the data is invalid\n     * @throws \\App\\Error\\Exception\\ValidationException if the data does not validate\n     * @throws \\Cake\\Http\\Exception\\InternalErrorException if data could not be saved because of an internal issue\n     * @throws \\Cake\\Http\\Exception\\NotFoundException if the key was not found\n     */\n    public function createMany(UserAccessControl $uac, MetadataPrivateKeysCreateManyDto $dto): void\n    {\n        $uac->assertIsAdmin();\n        if (empty($dto->getData())) {\n            return;\n        }","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/Metadata/src/Service/MetadataPrivateKeysCreateService.php#L138-L174","documentation":"After existence checks, assertRequestSanity queries MetadataPrivateKeysTable for an existing private key matching the metadata key and target (user, or server when user_id is null). If one already exists, the duplicate share attempt is rejected with a BadRequestException.","triggerScenarios":"POSTing a metadata private key for a user (or the server) that already has one for the same metadata key id — including retries of a successful earlier call.","commonSituations":"Non-idempotent retry logic resending the same share; two admins sharing the same key concurrently; client not updating its local copy of who has the key.","solutions":["Check for the existing metadata private key first (GET the key's private keys) and skip creation if present","Make the client operation idempotent: treat this 400 as 'already done' where appropriate","For re-sharing after rotation, delete/replace the old private key record or use the rotate flow instead of create"],"exampleFix":"// before\ncreate($uac, $keyId, $data); // retried blindly\n// after\nif (!$privateKeysService->existsForUser($keyId, $userId)) {\n    create($uac, $keyId, $data);\n}","handlingStrategy":"try-catch","validationCode":"const existing = await client.metadataPrivateKeys.list(keyId); if (existing.some(pk => pk.user_id === userId || pk.user_id === null)) return;","typeGuard":"const alreadyShared = (existing, userId) => existing.some(pk => (userId ? pk.user_id === userId : pk.user_id === null));","tryCatchPattern":"catch (e) { if (e.response?.status === 400 && /already shared/.test(e.response?.body?.message)) { return { status: 'already-shared' }; } throw e; }","preventionTips":["Make share operations idempotent by checking existing private keys first","Serialize concurrent share operations per key","Treat this 400 as success in retry loops"],"tags":["http-400","duplicate","idempotency","metadata"],"backgroundTag":"resource-already-exists","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T09:17:21.228Z"}