Update activitypub deliver

This commit is contained in:
Daniel Supernault 2019-03-07 23:46:38 -07:00
parent 691cc991c6
commit feeb60eb94
No known key found for this signature in database
GPG key ID: 0DEF1C662C9033F7
2 changed files with 133 additions and 135 deletions

View file

@ -63,8 +63,6 @@ class StatusActivityPubDeliver implements ShouldQueue
$profile = $status->profile; $profile = $status->profile;
Cache::forget('status:transformer:media:attachments:'.$status->id);
$fractal = new Fractal\Manager(); $fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer()); $fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($status, new CreateNote()); $resource = new Fractal\Resource\Item($status, new CreateNote());
@ -94,10 +92,8 @@ class StatusActivityPubDeliver implements ShouldQueue
$pool = new Pool($client, $requests($audience), [ $pool = new Pool($client, $requests($audience), [
'concurrency' => config('pixelfed.ap_delivery_concurrency'), 'concurrency' => config('pixelfed.ap_delivery_concurrency'),
'fulfilled' => function ($response, $index) { 'fulfilled' => function ($response, $index) {
Log::info('AP:deliver:success - ' . json_encode($response));
}, },
'rejected' => function ($reason, $index) { 'rejected' => function ($reason, $index) {
Log::info('AP:deliver:rejected - ' . json_encode($reason));
} }
]); ]);

View file

@ -4,13 +4,13 @@ namespace App\Util\ActivityPub;
use Cache, Purify, Storage, Request, Validator; use Cache, Purify, Storage, Request, Validator;
use App\{ use App\{
Activity, Activity,
Follower, Follower,
Like, Like,
Media, Media,
Notification, Notification,
Profile, Profile,
Status Status
}; };
use Zttp\Zttp; use Zttp\Zttp;
use Carbon\Carbon; use Carbon\Carbon;
@ -29,7 +29,6 @@ class Helpers {
public static function validateObject($data) public static function validateObject($data)
{ {
// todo: undo
$verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo']; $verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo'];
$valid = Validator::make($data, [ $valid = Validator::make($data, [
@ -57,30 +56,30 @@ class Helpers {
$activity = $data['object']; $activity = $data['object'];
$mediaTypes = ['Document', 'Image', 'Video']; $mediaTypes = ['Document', 'Image', 'Video'];
$mimeTypes = ['image/jpeg', 'image/png', 'video/mp4']; $mimeTypes = ['image/jpeg', 'image/png', 'video/mp4'];
if(!isset($activity['attachment']) || empty($activity['attachment'])) { if(!isset($activity['attachment']) || empty($activity['attachment'])) {
return false; return false;
} }
$attachment = $activity['attachment']; $attachment = $activity['attachment'];
$valid = Validator::make($attachment, [ $valid = Validator::make($attachment, [
'*.type' => [ '*.type' => [
'required', 'required',
'string', 'string',
Rule::in($mediaTypes) Rule::in($mediaTypes)
], ],
'*.url' => 'required|max:255', '*.url' => 'required|max:255',
'*.mediaType' => [ '*.mediaType' => [
'required', 'required',
'string', 'string',
Rule::in($mimeTypes) Rule::in($mimeTypes)
], ],
'*.name' => 'nullable|string|max:255' '*.name' => 'nullable|string|max:255'
])->passes(); ])->passes();
return $valid; return $valid;
} }
public static function normalizeAudience($data, $localOnly = true) public static function normalizeAudience($data, $localOnly = true)
@ -88,7 +87,7 @@ class Helpers {
if(!isset($data['to'])) { if(!isset($data['to'])) {
return; return;
} }
$audience = []; $audience = [];
$audience['to'] = []; $audience['to'] = [];
$audience['cc'] = []; $audience['cc'] = [];
@ -133,16 +132,16 @@ class Helpers {
public static function validateUrl($url) public static function validateUrl($url)
{ {
$localhosts = [ $localhosts = [
'127.0.0.1', 'localhost', '::1' '127.0.0.1', 'localhost', '::1'
]; ];
$valid = filter_var($url, FILTER_VALIDATE_URL); $valid = filter_var($url, FILTER_VALIDATE_URL);
if(in_array(parse_url($valid, PHP_URL_HOST), $localhosts)) { if(in_array(parse_url($valid, PHP_URL_HOST), $localhosts)) {
return false; return false;
} }
return $valid; return $valid;
} }
public static function validateLocalUrl($url) public static function validateLocalUrl($url)
@ -160,20 +159,20 @@ class Helpers {
public static function zttpUserAgent() public static function zttpUserAgent()
{ {
return [ return [
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelFedBot - https://pixelfed.org', 'User-Agent' => 'PixelFedBot - https://pixelfed.org',
]; ];
} }
public static function fetchFromUrl($url) public static function fetchFromUrl($url)
{ {
$res = Zttp::withHeaders(self::zttpUserAgent())->get($url); $res = Zttp::withHeaders(self::zttpUserAgent())->get($url);
$res = json_decode($res->body(), true, 8); $res = json_decode($res->body(), true, 8);
if(json_last_error() == JSON_ERROR_NONE) { if(json_last_error() == JSON_ERROR_NONE) {
return $res; return $res;
} else { } else {
return false; return false;
} }
} }
public static function fetchProfileFromUrl($url) public static function fetchProfileFromUrl($url)
@ -256,8 +255,8 @@ class Helpers {
$user = $status->profile; $user = $status->profile;
$monthHash = hash('sha1', date('Y').date('m')); $monthHash = hash('sha1', date('Y').date('m'));
$userHash = hash('sha1', $user->id.(string) $user->created_at); $userHash = hash('sha1', $user->id.(string) $user->created_at);
$storagePath = "public/m/{$monthHash}/{$userHash}"; $storagePath = "public/m/{$monthHash}/{$userHash}";
$allowed = explode(',', config('pixelfed.media_types')); $allowed = explode(',', config('pixelfed.media_types'));
foreach($attachments as $media) { foreach($attachments as $media) {
$type = $media['mediaType']; $type = $media['mediaType'];
$url = $media['url']; $url = $media['url'];
@ -265,28 +264,28 @@ class Helpers {
if(in_array($type, $allowed) == false || $valid == false) { if(in_array($type, $allowed) == false || $valid == false) {
continue; continue;
} }
$info = pathinfo($url); $info = pathinfo($url);
// pleroma attachment fix // pleroma attachment fix
$url = str_replace(' ', '%20', $url); $url = str_replace(' ', '%20', $url);
$img = file_get_contents($url, false, stream_context_create(['ssl' => ["verify_peer"=>false,"verify_peer_name"=>false]])); $img = file_get_contents($url, false, stream_context_create(['ssl' => ["verify_peer"=>false,"verify_peer_name"=>false]]));
$file = '/tmp/'.str_random(16).$info['basename']; $file = '/tmp/'.str_random(16).$info['basename'];
file_put_contents($file, $img); file_put_contents($file, $img);
$fdata = new File($file); $fdata = new File($file);
$path = Storage::putFile($storagePath, $fdata, 'public'); $path = Storage::putFile($storagePath, $fdata, 'public');
$media = new Media(); $media = new Media();
$media->status_id = $status->id; $media->status_id = $status->id;
$media->profile_id = $status->profile_id; $media->profile_id = $status->profile_id;
$media->user_id = null; $media->user_id = null;
$media->media_path = $path; $media->media_path = $path;
$media->size = $fdata->getSize(); $media->size = $fdata->getSize();
$media->mime = $fdata->getMimeType(); $media->mime = $fdata->getMimeType();
$media->save(); $media->save();
ImageThumbnail::dispatch($media); ImageThumbnail::dispatch($media);
ImageOptimize::dispatch($media); ImageOptimize::dispatch($media);
unlink($file); unlink($file);
} }
return; return;
} }
@ -301,82 +300,85 @@ class Helpers {
$id = last(explode('/', $url)); $id = last(explode('/', $url));
return Profile::whereUsername($id)->firstOrFail(); return Profile::whereUsername($id)->firstOrFail();
} }
$res = self::fetchProfileFromUrl($url); $res = self::fetchProfileFromUrl($url);
$domain = parse_url($res['id'], PHP_URL_HOST); if(isset($res['id']) == false) {
$username = $res['preferredUsername']; return;
$remoteUsername = "@{$username}@{$domain}"; }
$domain = parse_url($res['id'], PHP_URL_HOST);
$username = $res['preferredUsername'];
$remoteUsername = "@{$username}@{$domain}";
$profile = Profile::whereRemoteUrl($res['id'])->first(); $profile = Profile::whereRemoteUrl($res['id'])->first();
if(!$profile) { if(!$profile) {
$profile = new Profile; $profile = new Profile;
$profile->domain = $domain; $profile->domain = $domain;
$profile->username = $remoteUsername; $profile->username = $remoteUsername;
$profile->name = strip_tags($res['name']); $profile->name = strip_tags($res['name']);
$profile->bio = Purify::clean($res['summary']); $profile->bio = Purify::clean($res['summary']);
$profile->sharedInbox = isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null; $profile->sharedInbox = isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null;
$profile->inbox_url = $res['inbox']; $profile->inbox_url = $res['inbox'];
$profile->outbox_url = $res['outbox']; $profile->outbox_url = $res['outbox'];
$profile->remote_url = $res['id']; $profile->remote_url = $res['id'];
$profile->public_key = $res['publicKey']['publicKeyPem']; $profile->public_key = $res['publicKey']['publicKeyPem'];
$profile->key_id = $res['publicKey']['id']; $profile->key_id = $res['publicKey']['id'];
$profile->save(); $profile->save();
if($runJobs == true) { if($runJobs == true) {
RemoteFollowImportRecent::dispatch($res, $profile); RemoteFollowImportRecent::dispatch($res, $profile);
CreateAvatar::dispatch($profile); CreateAvatar::dispatch($profile);
} }
} }
return $profile; return $profile;
} }
public static function sendSignedObject($senderProfile, $url, $body) public static function sendSignedObject($senderProfile, $url, $body)
{ {
$payload = json_encode($body); $payload = json_encode($body);
$headers = HttpSignature::sign($senderProfile, $url, $body); $headers = HttpSignature::sign($senderProfile, $url, $body);
$ch = curl_init($url); $ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_HEADER, true);
$response = curl_exec($ch); $response = curl_exec($ch);
return; return;
} }
private static function _headersToSigningString($headers) { private static function _headersToSigningString($headers) {
} }
public static function validateSignature($request, $payload = null) public static function validateSignature($request, $payload = null)
{ {
} }
public static function fetchPublicKey() public static function fetchPublicKey()
{ {
$profile = $this->profile; $profile = $this->profile;
$is_url = $this->is_url; $is_url = $this->is_url;
$valid = $this->validateUrl(); $valid = $this->validateUrl();
if (!$valid) { if (!$valid) {
throw new \Exception('Invalid URL provided'); throw new \Exception('Invalid URL provided');
} }
if ($is_url && isset($profile->public_key) && $profile->public_key) { if ($is_url && isset($profile->public_key) && $profile->public_key) {
return $profile->public_key; return $profile->public_key;
} }
try { try {
$url = $this->profile; $url = $this->profile;
$res = Zttp::timeout(30)->withHeaders([ $res = Zttp::timeout(30)->withHeaders([
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org', 'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org',
])->get($url); ])->get($url);
$actor = json_decode($res->getBody(), true); $actor = json_decode($res->getBody(), true);
} catch (Exception $e) { } catch (Exception $e) {
throw new Exception('Unable to fetch public key'); throw new Exception('Unable to fetch public key');
} }
if($actor['publicKey']['owner'] != $profile) { if($actor['publicKey']['owner'] != $profile) {
throw new Exception('Invalid key match'); throw new Exception('Invalid key match');
} }
$this->public_key = $actor['publicKey']['publicKeyPem']; $this->public_key = $actor['publicKey']['publicKeyPem'];
$this->key_id = $actor['publicKey']['id']; $this->key_id = $actor['publicKey']['id'];
return $this; return $this;
} }
} }