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;
}
}
