forked from grumpydevelop/federator

- fixed rewrites to properly support @username/outbox, username/outbox, users/username/outbox, ... - initial support for sending follow to e.g. mastodon (code commented out in api.php) - database migration for follows table and fedusers table - save retrieved fedusers in cache and db - depend more on configs externaldomain, less on server_name - properly implemented following-logic in dio - made postForuUser static in newContent and inbox - provide rsaprivate for signing reuests -> actPub-Servers - change contenttype depending on use-case - changed user json-template to have loop-back between user-template and webfinger (mastodon f.e. needs this)
176 lines
5.6 KiB
PHP
176 lines
5.6 KiB
PHP
<?php
|
|
/**
|
|
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* @author Yannis Vogel (vogeldevelopment)
|
|
**/
|
|
|
|
namespace Federator\Api\FedUsers;
|
|
|
|
/**
|
|
* handle activitypub inbox requests
|
|
*/
|
|
class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|
{
|
|
/**
|
|
* main instance
|
|
*
|
|
* @var \Federator\Api $main
|
|
*/
|
|
private $main;
|
|
|
|
/**
|
|
* constructor
|
|
* @param \Federator\Api $main api main instance
|
|
*/
|
|
public function __construct($main)
|
|
{
|
|
$this->main = $main;
|
|
}
|
|
|
|
/**
|
|
* handle get call
|
|
*
|
|
* @param string|null $_user user to fetch inbox for @unused-param
|
|
* @return string|false response
|
|
*/
|
|
public function get($_user)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* handle post call
|
|
*
|
|
* @param string|null $_user user to add data to inbox
|
|
* @return string|false response
|
|
*/
|
|
public function post($_user)
|
|
{
|
|
$_rawInput = file_get_contents('php://input');
|
|
|
|
$allHeaders = getallheaders();
|
|
try {
|
|
$this->main->checkSignature($allHeaders);
|
|
} catch (\Federator\Exceptions\PermissionDenied $e) {
|
|
error_log("Inbox::post Signature check failed: " . $e->getMessage());
|
|
http_response_code(401);
|
|
return false;
|
|
}
|
|
|
|
$activity = is_string($_rawInput) ? json_decode($_rawInput, true) : null;
|
|
|
|
if (!is_array($activity)) {
|
|
error_log("Inbox::post Input wasn't of type array");
|
|
return false;
|
|
}
|
|
|
|
$inboxActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($activity);
|
|
|
|
$rootDir = $_SERVER['DOCUMENT_ROOT'] . '../';
|
|
|
|
// Shared inbox
|
|
if (!isset($_user)) {
|
|
// Save the raw input and parsed JSON to a file for inspection
|
|
file_put_contents(
|
|
$rootDir . 'logs/inbox.log',
|
|
date('Y-m-d H:i:s') . ": ==== POST Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
|
FILE_APPEND
|
|
);
|
|
}
|
|
|
|
if ($inboxActivity === false) {
|
|
error_log("Inbox::post couldn't create inboxActivity, aborting");
|
|
return false;
|
|
}
|
|
|
|
$sendTo = $inboxActivity->getCC();
|
|
if ($inboxActivity->getType() === 'Undo') { // for undo the object holds the proper cc
|
|
$object = $inboxActivity->getObject();
|
|
if ($object !== null && is_object($object)) {
|
|
$sendTo = $object->getCC();
|
|
}
|
|
}
|
|
|
|
$users = [];
|
|
$dbh = $this->main->getDatabase();
|
|
$cache = $this->main->getCache();
|
|
$connector = $this->main->getConnector();
|
|
|
|
foreach ($sendTo as $receiver) {
|
|
if ($receiver === '' || !is_string($receiver)) {
|
|
continue;
|
|
}
|
|
|
|
if (str_ends_with($receiver, '/followers')) {
|
|
$actor = $inboxActivity->getAActor();
|
|
if ($actor === null || !is_string($actor)) {
|
|
error_log("Inbox::post no actor found");
|
|
continue;
|
|
}
|
|
|
|
// Extract username from the actor URL
|
|
$username = basename((string)(parse_url($actor, PHP_URL_PATH) ?? ''));
|
|
$domain = parse_url($actor, PHP_URL_HOST);
|
|
if ($username === null || $domain === null) {
|
|
error_log("Inbox::post no username or domain found");
|
|
continue;
|
|
}
|
|
$followers = \Federator\DIO\Followers::getFollowersByFedUser($dbh, $connector, $cache, $username . '@' . $domain);
|
|
|
|
if (is_array($followers)) {
|
|
$users = array_merge($users, array_column($followers, 'id'));
|
|
}
|
|
}
|
|
}
|
|
if ($_user !== false && !in_array($_user, $users, true)) {
|
|
$users[] = $_user;
|
|
}
|
|
foreach ($users as $user) {
|
|
if (!isset($user)) {
|
|
continue;
|
|
}
|
|
|
|
$this->postForUser($dbh, $connector, $cache, $user, $inboxActivity);
|
|
}
|
|
return json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
|
}
|
|
|
|
/**
|
|
* handle post call for specific user
|
|
*
|
|
* @param \mysqli $dbh database handle
|
|
* @param \Federator\Connector\Connector $connector connector to use
|
|
* @param \Federator\Cache\Cache|null $cache optional caching service
|
|
* @param string $_user user to add data to inbox
|
|
* @param \Federator\Data\ActivityPub\Common\Activity $inboxActivity the activity that we received
|
|
* @return boolean response
|
|
*/
|
|
private static function postForUser($dbh, $connector, $cache, $_user, $inboxActivity)
|
|
{
|
|
if (isset($_user)) {
|
|
// get user
|
|
$user = \Federator\DIO\User::getUserByName(
|
|
$dbh,
|
|
$_user,
|
|
$connector,
|
|
$cache
|
|
);
|
|
if ($user === null || $user->id === null) {
|
|
error_log("Inbox::postForUser couldn't find user: $_user");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$rootDir = $_SERVER['DOCUMENT_ROOT'] . '../';
|
|
// Save the raw input and parsed JSON to a file for inspection
|
|
file_put_contents(
|
|
$rootDir . 'logs/inbox_' . $_user . '.log',
|
|
date('Y-m-d H:i:s') . ": ==== POST " . $_user . " Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
|
FILE_APPEND
|
|
);
|
|
|
|
return true;
|
|
}
|
|
}
|