pixelfed/app/Http/Controllers/FederationController.php

254 lines
8.6 KiB
PHP
Raw Normal View History

2018-05-28 23:50:14 +00:00
<?php
namespace App\Http\Controllers;
2019-06-10 00:51:54 +00:00
use App\Jobs\InboxPipeline\{
InboxWorker,
InboxValidator
};
2018-08-28 03:07:36 +00:00
use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline;
2019-03-02 03:08:52 +00:00
use App\{
AccountLog,
Like,
Profile,
Status,
User
2019-03-02 03:08:52 +00:00
};
2018-06-01 02:38:11 +00:00
use App\Util\Lexer\Nickname;
use App\Util\Webfinger\Webfinger;
2018-08-28 03:07:36 +00:00
use Auth;
use Cache;
use Carbon\Carbon;
use Illuminate\Http\Request;
use League\Fractal;
2019-12-21 06:15:15 +00:00
use App\Util\Site\Nodeinfo;
2019-06-10 00:51:54 +00:00
use App\Util\ActivityPub\{
Helpers,
2019-12-21 06:15:15 +00:00
HttpSignature,
Outbox
2019-06-10 00:51:54 +00:00
};
2020-02-22 11:33:35 +00:00
use Zttp\Zttp;
2018-05-28 23:50:14 +00:00
class FederationController extends Controller
{
public function nodeinfoWellKnown()
{
2019-06-09 23:15:35 +00:00
abort_if(!config('federation.nodeinfo.enabled'), 404);
2019-12-21 06:15:15 +00:00
return response()->json(Nodeinfo::wellKnown());
2018-05-28 23:50:14 +00:00
}
public function nodeinfo()
{
2019-06-09 23:15:35 +00:00
abort_if(!config('federation.nodeinfo.enabled'), 404);
2019-12-21 06:15:15 +00:00
return response()->json(Nodeinfo::get())
->header('Access-Control-Allow-Origin','*');
2018-08-28 03:07:36 +00:00
}
2018-06-01 02:38:11 +00:00
public function webfinger(Request $request)
{
2019-06-09 23:15:35 +00:00
abort_if(!config('federation.webfinger.enabled'), 404);
2018-08-28 03:07:36 +00:00
$this->validate($request, ['resource'=>'required|string|min:3|max:255']);
2018-12-24 21:36:25 +00:00
$resource = $request->input('resource');
$parsed = Nickname::normalizeProfileUrl($resource);
2019-07-16 01:22:48 +00:00
if($parsed['domain'] !== config('pixelfed.domain.app')) {
abort(404);
}
2018-12-24 21:36:25 +00:00
$username = $parsed['username'];
2019-07-16 01:22:48 +00:00
$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
2018-12-24 21:36:25 +00:00
if($profile->status != null) {
return ProfileController::accountCheck($profile);
}
$webfinger = (new Webfinger($profile))->generate();
2018-08-28 03:07:36 +00:00
return response()->json($webfinger, 200, [], JSON_PRETTY_PRINT);
2018-06-01 02:38:11 +00:00
}
public function hostMeta(Request $request)
{
2019-06-09 23:15:35 +00:00
abort_if(!config('federation.webfinger.enabled'), 404);
$path = route('well-known.webfinger');
$xml = '<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" type="application/xrd+xml" template="'.$path.'?resource={uri}"/></XRD>';
return response($xml)->header('Content-Type', 'application/xrd+xml');
}
2018-06-01 02:38:11 +00:00
public function userOutbox(Request $request, $username)
{
2019-06-09 23:15:35 +00:00
abort_if(!config('federation.activitypub.enabled'), 404);
abort_if(!config('federation.activitypub.outbox'), 404);
2018-08-28 03:07:36 +00:00
2020-04-12 04:12:28 +00:00
$profile = Profile::whereNull('domain')
->whereNull('status')
->whereIsPrivate(false)
->whereUsername($username)
->firstOrFail();
$key = 'ap:outbox:latest_10:pid:' . $profile->id;
$ttl = now()->addMinutes(15);
$res = Cache::remember($key, $ttl, function() use($profile) {
return Outbox::get($profile);
});
return response(json_encode($res, JSON_UNESCAPED_SLASHES))->header('Content-Type', 'application/activity+json');
2018-06-01 02:38:11 +00:00
}
2018-07-24 03:20:46 +00:00
public function userInbox(Request $request, $username)
{
2019-06-09 23:15:35 +00:00
abort_if(!config('federation.activitypub.enabled'), 404);
abort_if(!config('federation.activitypub.inbox'), 404);
2019-06-10 18:36:50 +00:00
// $headers = $request->headers->all();
// $payload = $request->getContent();
// InboxValidator::dispatch($username, $headers, $payload);
$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
if($profile->status != null) {
return ProfileController::accountCheck($profile);
}
$body = $request->getContent();
$bodyDecoded = json_decode($body, true, 8);
if($this->verifySignature($request, $profile) == true) {
InboxWorker::dispatch($request->headers->all(), $profile, $bodyDecoded);
} else if($this->blindKeyRotation($request, $profile) == true) {
InboxWorker::dispatch($request->headers->all(), $profile, $bodyDecoded);
} else {
abort(400, 'Bad Signature');
}
2018-12-30 04:50:37 +00:00
return;
}
2019-06-10 01:23:13 +00:00
2018-12-30 04:50:37 +00:00
protected function verifySignature(Request $request, Profile $profile)
{
2019-06-10 18:36:50 +00:00
$body = $request->getContent();
$bodyDecoded = json_decode($body, true, 8);
$signature = $request->header('signature');
$date = $request->header('date');
2019-12-14 20:02:24 +00:00
$digest = $request->header('digest');
if(!$digest) {
abort(400, 'Missing digest header');
}
2019-06-10 18:36:50 +00:00
if(!$signature) {
abort(400, 'Missing signature header');
}
if(!$date) {
abort(400, 'Missing date header');
}
if(!now()->parse($date)->gt(now()->subDays(1)) || !now()->parse($date)->lt(now()->addDays(1))) {
abort(400, 'Invalid date');
}
$signatureData = HttpSignature::parseSignatureHeader($signature);
$keyId = Helpers::validateUrl($signatureData['keyId']);
$id = Helpers::validateUrl($bodyDecoded['id']);
$keyDomain = parse_url($keyId, PHP_URL_HOST);
$idDomain = parse_url($id, PHP_URL_HOST);
2019-08-30 03:04:10 +00:00
if($keyDomain == config('pixelfed.domain.app') || $idDomain == config('pixelfed.domain.app')) {
return false;
}
2019-06-10 18:36:50 +00:00
if(isset($bodyDecoded['object'])
&& is_array($bodyDecoded['object'])
&& isset($bodyDecoded['object']['attributedTo'])
) {
if(parse_url($bodyDecoded['object']['attributedTo'], PHP_URL_HOST) !== $keyDomain) {
abort(400, 'Invalid request');
}
}
if(!$keyDomain || !$idDomain || $keyDomain !== $idDomain) {
abort(400, 'Invalid request');
}
$actor = Profile::whereKeyId($keyId)->first();
if(!$actor) {
$actor = Helpers::profileFirstOrNew($bodyDecoded['actor']);
}
if(!$actor) {
return false;
}
$pkey = openssl_pkey_get_public($actor->public_key);
$inboxPath = "/users/{$profile->username}/inbox";
2019-08-30 03:04:10 +00:00
list($verified, $headers) = HttpSignature::verify($pkey, $signatureData, $request->headers->all(), $inboxPath, $body);
2019-06-10 18:36:50 +00:00
if($verified == 1) {
return true;
} else {
return false;
}
2018-12-30 04:50:37 +00:00
}
protected function blindKeyRotation(Request $request, Profile $profile)
{
2019-06-10 18:36:50 +00:00
$signature = $request->header('signature');
$date = $request->header('date');
if(!$signature) {
abort(400, 'Missing signature header');
}
if(!$date) {
abort(400, 'Missing date header');
}
if(!now()->parse($date)->gt(now()->subDays(1)) || !now()->parse($date)->lt(now()->addDays(1))) {
abort(400, 'Invalid date');
}
$signatureData = HttpSignature::parseSignatureHeader($signature);
$keyId = Helpers::validateUrl($signatureData['keyId']);
$actor = Profile::whereKeyId($keyId)->whereNotNull('remote_url')->firstOrFail();
$res = Zttp::timeout(5)->withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
])->get($actor->remote_url);
$res = json_decode($res->body(), true, 8);
if($res['publicKey']['id'] !== $actor->key_id) {
return false;
}
$actor->public_key = $res['publicKey']['publicKeyPem'];
$actor->save();
return $this->verifySignature($request, $profile);
}
public function userFollowing(Request $request, $username)
{
2019-06-09 23:15:35 +00:00
abort_if(!config('federation.activitypub.enabled'), 404);
2018-12-15 08:16:22 +00:00
$profile = Profile::whereNull('remote_url')
->whereUsername($username)
->whereIsPrivate(false)
->firstOrFail();
2019-06-10 01:23:13 +00:00
2018-12-24 21:36:25 +00:00
if($profile->status != null) {
2019-07-16 01:22:48 +00:00
abort(404);
2018-12-24 21:36:25 +00:00
}
2019-07-16 01:22:48 +00:00
$obj = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $request->getUri(),
'type' => 'OrderedCollectionPage',
2019-07-16 01:22:48 +00:00
'totalItems' => 0,
'orderedItems' => []
];
return response()->json($obj);
}
public function userFollowers(Request $request, $username)
{
2019-06-09 23:15:35 +00:00
abort_if(!config('federation.activitypub.enabled'), 404);
2018-12-15 08:16:22 +00:00
$profile = Profile::whereNull('remote_url')
->whereUsername($username)
->whereIsPrivate(false)
->firstOrFail();
2019-06-10 01:23:13 +00:00
2018-12-24 21:36:25 +00:00
if($profile->status != null) {
2019-07-16 01:22:48 +00:00
abort(404);
2018-12-24 21:36:25 +00:00
}
2019-07-16 01:22:48 +00:00
$obj = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $request->getUri(),
'type' => 'OrderedCollectionPage',
2019-07-16 01:22:48 +00:00
'totalItems' => 0,
'orderedItems' => []
];
2019-07-16 01:22:48 +00:00
return response()->json($obj);
2018-07-24 03:20:46 +00:00
}
2018-05-28 23:50:14 +00:00
}