diff --git a/app/Console/Commands/User/UserAccountDelete.php b/app/Console/Commands/User/UserAccountDelete.php index 07acdc6a6..c6206492b 100644 --- a/app/Console/Commands/User/UserAccountDelete.php +++ b/app/Console/Commands/User/UserAccountDelete.php @@ -322,7 +322,13 @@ class UserAccountDelete extends Command $responses = Http::pool(function (Pool $pool) use ($urlList, $privateKey, $keyId, $digest, $payload, $payloadLen) { foreach ($urlList as $url) { - $headers = HttpSignature::signRawWithDigest($privateKey, $keyId, $url, $digest); + // Pass User-Agent/Accept per request so they are actually sent + // (and signed); Http::pool does not inherit the makeHttpClient + // instance headers, so without this Guzzle sends its default UA. + $headers = HttpSignature::signRawWithDigest($privateKey, $keyId, $url, $digest, [ + 'User-Agent' => 'Pixelfed ('.config('app.url').')', + 'Accept' => 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + ]); $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; $headers['Content-Length'] = (string) $payloadLen; @@ -392,7 +398,10 @@ class UserAccountDelete extends Command protected function sendDebug(string $url, string $payload, string $digest, string $privateKey, string $keyId): int { - $headers = HttpSignature::signRawWithDigest($privateKey, $keyId, $url, $digest); + $headers = HttpSignature::signRawWithDigest($privateKey, $keyId, $url, $digest, [ + 'User-Agent' => 'Pixelfed ('.config('app.url').')', + 'Accept' => 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + ]); $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; diff --git a/tests/Feature/Console/UserAccountDeleteUserAgentTest.php b/tests/Feature/Console/UserAccountDeleteUserAgentTest.php new file mode 100644 index 000000000..a641f3dbb --- /dev/null +++ b/tests/Feature/Console/UserAccountDeleteUserAgentTest.php @@ -0,0 +1,72 @@ + 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); + openssl_pkey_export($res, $pem); + + return $pem; +} + +it('sends the Pixelfed User-Agent on pooled delete deliveries', function () { + Http::fake([ + '*' => Http::response('', 202), + ]); + + $privateKey = generatePrivateKeyPem(); + $keyId = 'https://local.test/users/alice#main-key'; + $payload = '{"type":"Delete"}'; + $digest = base64_encode(hash('sha256', $payload, true)); + + $urls = collect(['https://remote.example/f/inbox']); + + $command = app(UserAccountDelete::class); + $ref = new ReflectionMethod($command, 'sendBatch'); + $ref->setAccessible(true); + + // sendBatch(PendingRequest $client, string $privateKey, string $keyId, + // string $digest, Collection $urls, string $payload, int $payloadLen, + // int $concurrency, bool $verboseErrors) + $client = (new ReflectionMethod($command, 'makeHttpClient')); + $client->setAccessible(true); + $pendingClient = $client->invoke($command); + + $ref->invoke( + $command, + $pendingClient, + $privateKey, + $keyId, + $digest, + $urls, + $payload, + strlen($payload), + 5, + false + ); + + Http::assertSent(function ($request) { + return $request->url() === 'https://remote.example/f/inbox' + && $request->hasHeader('User-Agent') + && str_contains($request->header('User-Agent')[0], 'Pixelfed'); + }); +});