activitypub-1.0.x-dev/src/Controller/InboxController.php
src/Controller/InboxController.php
<?php namespace Drupal\activitypub\Controller; use Drupal\activitypub\Entity\ActivityPubActivityInterface; use Drupal\activitypub\Entity\ActivitypubActorInterface; use Drupal\user\UserInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; class InboxController extends BaseController { /** * Inbox routing callback. * * @param \Symfony\Component\HttpFoundation\Request $request * @param \Drupal\user\UserInterface $user * @param \Drupal\activitypub\Entity\ActivitypubActorInterface $activitypub_actor * * @return \Symfony\Component\HttpFoundation\JsonResponse */ public function inbox(Request $request, UserInterface $user, ActivitypubActorInterface $activitypub_actor) { $status = 400; $payload = @json_decode((string)$request->getContent(), TRUE); $actor = !empty($payload['actor']) ? $payload['actor'] : ''; $id = !empty($payload['id']) ? $payload['id'] : ''; if ($request->getMethod() == 'POST' && !empty($payload) && !empty($actor) && !empty($id)) { try { if (!$this->domainIsBlocked($activitypub_actor->getBlockedDomains(), $actor)) { $log_error_signature = $this->config('activitypub.settings')->get('log_error_signature'); $log_success_signature = $this->config('activitypub.settings')->get('log_success_signature'); $log_followee_check = $this->config('activitypub.settings')->get('log_followee_check'); // The signature check is used to set the status of the activity. // There is a big chance some might fail depending how the request is // signed and which rfc version is used. In case the verification // fails, we allow posts to be followed in case the actor is a // followee of the current user. try { $published = $this->activityPubSignature->verifySignature($request, $actor, $this->activityPubUtility->getServer()); if ($published) { if ($log_success_signature) { $this->getLogger('activitypub')->notice('Signature verified for id @id', ['@id' => $id]); } } elseif (isset($payload['type']) && in_array($payload['type'], $this->activityPubUtility->getTimelineTypes())) { $published = $this->isFollowee($actor, $activitypub_actor->getOwnerId()); if ($log_followee_check) { if ($published) { $this->getLogger('activitypub')->notice('Post published: @actor follows @followee', ['@actor' => $activitypub_actor->getName(), '@followee' => $payload['actor']]); } else { $this->getLogger('activitypub')->notice('Post left unpublished: @actor does not follow @followee', ['@actor' => $activitypub_actor->getName(), '@followee' => $payload['actor']]); } } } } catch (\Exception $e) { $published = FALSE; if ($log_error_signature) { $this->getLogger('activitypub')->error('Inbox signature/followee exception: @message', ['@message' => $e->getMessage()]); } } // Get the object. $object = $this->getObject($payload); /** @var \Drupal\activitypub\Entity\Storage\ActivityPubActivityStorage $storage */ $storage = $this->entityTypeManager()->getStorage('activitypub_activity'); $values = [ 'uid' => $user->id(), 'collection' => ActivityPubActivityInterface::INBOX, 'external_id' => $id, 'type' => $payload['type'], 'actor' => $actor, 'object' => $object, 'payload' => (string) $request->getContent(), 'status' => $published, ]; if (is_array($payload['object']) && !empty($payload['object']['inReplyTo'])) { $values['reply'] = $payload['object']['inReplyTo']; } /** @var \Drupal\activitypub\Entity\ActivityPubActivityInterface $activity */ $activity = $storage->create($values); $doSave = TRUE; $activity->preInboxSave($doSave); if ($doSave) { $activity->save(); } elseif ($this->config('activitypub.settings')->get('log_unsaved_inbox_activity')) { $this->getLogger('activitypub')->notice('Not saved - Payload: @payload', ['@payload' => print_r($payload, 1)]); } $status = 202; } else { $status = 403; } } catch (\Exception $e) { if ($this->config('activitypub.settings')->get('log_general_inbox_error')) { $this->getLogger('activitypub')->error('Inbox general error: @message - @content', ['@message' => $e->getMessage(), '@content' => (string) $request->getContent()]); } } } return new JsonResponse(NULL, $status); } /** * Returns if the actor is followed by the user. * * @param $followee * @param $uid * * @return bool * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ protected function isFollowee($followee, $uid) { $conditions = [ 'type' => 'Follow', 'status' => 1, 'object' => $followee, 'uid' => $uid, 'collection' => ActivityPubActivityInterface::OUTBOX, ]; /** @var \Drupal\activitypub\Entity\Storage\ActivityPubActivityStorage $storage */ $storage = $this->entityTypeManager()->getStorage('activitypub_activity'); $count = $storage->getActivityCount($conditions); return $count === 1; } /** * Returns whether the actor is blocked or not. * * @param $domains * @param $actor * * @return false */ protected function domainIsBlocked($domains, $actor) { $blocked = FALSE; if (!empty($domains)) { $blocked = $this->pathMatcher->matchPath($actor, $domains); } return $blocked; } /** * Gets the object. * * @param $payload * * @return mixed|string */ protected function getObject($payload) { $object = ''; if (isset($payload['object'])) { if (is_array($payload['object']) && isset($payload['object']['object'])) { if ($payload['type'] == 'Accept' && isset($payload['object']['actor'])) { $object = $payload['object']['actor']; } else { $object = $payload['object']['object']; } } elseif (is_array($payload['object']) && isset($payload['object']['id'])) { $object = $payload['object']['id']; } elseif ($payload['type'] == 'Move' && !empty($payload['target'])) { $object = $payload['target']; } elseif (is_string($payload['object'])) { $object = $payload['object']; } } return $object; } }