diff --git a/.phan/config.php b/.phan/config.php index 611a873..b79795c 100644 --- a/.phan/config.php +++ b/.phan/config.php @@ -359,6 +359,7 @@ return [ 'directory_list' => [ 'vendor/phan/phan/src/Phan', 'vendor/smarty/smarty/src', + 'vendor/resque/php-resque/lib', 'php/', 'plugins', 'htdocs', diff --git a/composer.json b/composer.json index 5deb518..2a12bd1 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,8 @@ "description": "A federation service", "type": "project", "require": { - "smarty/smarty": "^5.3" + "smarty/smarty": "^5.3", + "resque/php-resque": "^1.3.6" }, "license": "GPL-3.0-or-later", "authors": [ @@ -14,5 +15,10 @@ ], "require-dev": { "phan/phan": "^5.4" + }, + "autoload": { + "psr-4": { + "Federator\\": "php/federator/" + } } } diff --git a/config.ini b/config.ini index 07fa4a3..37cd6e8 100644 --- a/config.ini +++ b/config.ini @@ -8,8 +8,8 @@ password = '*change*me*' database = 'federator' [templates] -path = '../templates/federator/' -compiledir = '../cache' +path = 'templates/federator/' +compiledir = 'cache' [plugins] rediscache = 'rediscache.php' @@ -21,5 +21,5 @@ username = 'federatoradmin' password = '*change*me*as*well' [keys] -federatorPrivateKeyPath = '../federator.key' -federatorPublicKeyPath = '../federator.pub' \ No newline at end of file +federatorPrivateKeyPath = 'federator.key' +federatorPublicKeyPath = 'federator.pub' \ No newline at end of file diff --git a/contentnation.ini b/contentnation.ini index 9f56c3f..6e6e082 100644 --- a/contentnation.ini +++ b/contentnation.ini @@ -7,4 +7,4 @@ url = 'https://userdata.contentnation.net' [keys] headerSenderName = 'contentnation' -publicKeyPath = '../contentnation.pub' \ No newline at end of file +publicKeyPath = 'contentnation.pub' \ No newline at end of file diff --git a/htdocs/federator.php b/htdocs/federator.php index 5bf4f65..7b2cbe5 100644 --- a/htdocs/federator.php +++ b/htdocs/federator.php @@ -14,6 +14,7 @@ date_default_timezone_set("Europe/Berlin"); spl_autoload_register(static function (string $className) { include '../php/' . str_replace("\\", "/", strtolower($className)) . '.php'; }); +define('PROJECT_ROOT', dirname(__DIR__, 1)); /// main instance $contentnation = new \Federator\Api(); diff --git a/php/federator/api/fedusers/inbox.php b/php/federator/api/fedusers/inbox.php index 970ad5e..c16f4e7 100644 --- a/php/federator/api/fedusers/inbox.php +++ b/php/federator/api/fedusers/inbox.php @@ -54,21 +54,22 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface 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; + throw new \Federator\Exceptions\Unauthorized("Inbox::post Signature check failed: " . $e->getMessage()); } $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; + throw new \Federator\Exceptions\ServerError("Inbox::post Input wasn't of type array"); } $inboxActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($activity); - $rootDir = $_SERVER['DOCUMENT_ROOT'] . '../'; + if ($inboxActivity === false) { + throw new \Federator\Exceptions\ServerError("Inbox::post couldn't create inboxActivity"); + } + + $rootDir = PROJECT_ROOT . '/'; // Shared inbox if (!isset($_user)) { @@ -80,11 +81,6 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface ); } - 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(); @@ -132,9 +128,13 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface continue; } - $this->postForUser($dbh, $connector, $cache, $user, $inboxActivity); + $token = \Resque::enqueue('inbox', 'Federator\\Jobs\\InboxJob', [ + 'user' => $user, + 'activity' => $inboxActivity->toObject(), + ]); + error_log("Inbox::post enqueued job for user: $user with token: $token"); } - return json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + return "success"; } /** @@ -147,7 +147,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface * @param \Federator\Data\ActivityPub\Common\Activity $inboxActivity the activity that we received * @return boolean response */ - private static function postForUser($dbh, $connector, $cache, $_user, $inboxActivity) + public static function postForUser($dbh, $connector, $cache, $_user, $inboxActivity) { if (isset($_user)) { // get user @@ -158,12 +158,11 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface $cache ); if ($user === null || $user->id === null) { - error_log("Inbox::postForUser couldn't find user: $_user"); - return false; + throw new \Federator\Exceptions\ServerError("Inbox::postForUser couldn't find user: $_user"); } } - $rootDir = $_SERVER['DOCUMENT_ROOT'] . '../'; + $rootDir = PROJECT_ROOT . '/'; // Save the raw input and parsed JSON to a file for inspection file_put_contents( $rootDir . 'logs/inbox_' . $_user . '.log', diff --git a/php/federator/api/v1/newcontent.php b/php/federator/api/v1/newcontent.php index 3a6b01b..307e337 100644 --- a/php/federator/api/v1/newcontent.php +++ b/php/federator/api/v1/newcontent.php @@ -131,7 +131,7 @@ class NewContent implements \Federator\Api\APIInterface } if (empty($users)) { // todo remove after proper implementation, debugging for now - $rootDir = $_SERVER['DOCUMENT_ROOT'] . '../'; + $rootDir = PROJECT_ROOT . '/'; // Save the raw input and parsed JSON to a file for inspection file_put_contents( $rootDir . 'logs/newContent.log', @@ -186,7 +186,7 @@ class NewContent implements \Federator\Api\APIInterface return false; } - $rootDir = $_SERVER['DOCUMENT_ROOT'] . '../'; + $rootDir = PROJECT_ROOT . '/'; // Save the raw input and parsed JSON to a file for inspection file_put_contents( $rootDir . 'logs/newcontent_' . $_user . '.log', diff --git a/php/federator/data/activitypub/common/accept.php b/php/federator/data/activitypub/common/accept.php index c0eef65..ec395ff 100644 --- a/php/federator/data/activitypub/common/accept.php +++ b/php/federator/data/activitypub/common/accept.php @@ -12,7 +12,7 @@ class Accept extends Activity { public function __construct() { - parent::__construct('Accept'); - parent::addContext('https://www.w3.org/ns/activitystreams'); + parent::__construct('Accept'); + parent::addContext('https://www.w3.org/ns/activitystreams'); } } diff --git a/php/federator/data/activitypub/common/delete.php b/php/federator/data/activitypub/common/delete.php index cdceb00..bffb89f 100644 --- a/php/federator/data/activitypub/common/delete.php +++ b/php/federator/data/activitypub/common/delete.php @@ -15,29 +15,29 @@ class Delete extends Activity * @var string */ private $object = ""; - + public function setFObject(string $object): void { - $this->object = $object; + $this->object = $object; } - + public function __construct() { - parent::__construct('Delete'); - parent::addContext('https://www.w3.org/ns/activitystreams'); + parent::__construct('Delete'); + parent::addContext('https://www.w3.org/ns/activitystreams'); } - + /** * create from json/array * @param mixed $json */ public function fromJson($json): bool { - if (array_key_exists('object', $json)) { - $this->object = $json['object']; - unset($json['object']); - } - return parent::fromJson($json); + if (array_key_exists('object', $json)) { + $this->object = $json['object']; + unset($json['object']); + } + return parent::fromJson($json); } /** * convert internal state to php array @@ -45,10 +45,10 @@ class Delete extends Activity */ public function toObject() { - $return = parent::toObject(); - if ($this->object !== "") { - $return['object'] = $this->object; - } - return $return; + $return = parent::toObject(); + if ($this->object !== "") { + $return['object'] = $this->object; + } + return $return; } } diff --git a/php/federator/data/activitypub/common/reject.php b/php/federator/data/activitypub/common/reject.php index 66424f9..fb7b1ab 100644 --- a/php/federator/data/activitypub/common/reject.php +++ b/php/federator/data/activitypub/common/reject.php @@ -12,7 +12,7 @@ class Reject extends Activity { public function __construct() { - parent::__construct('Reject'); - parent::addContext('https://www.w3.org/ns/activitystreams'); + parent::__construct('Reject'); + parent::addContext('https://www.w3.org/ns/activitystreams'); } } diff --git a/php/federator/jobs/inboxJob.php b/php/federator/jobs/inboxJob.php new file mode 100644 index 0000000..b02c313 --- /dev/null +++ b/php/federator/jobs/inboxJob.php @@ -0,0 +1,67 @@ + $args Arguments for the job */ + public $args = []; + + /** + * cache instance + * + * @var \Federator\Cache\Cache $cache + */ + protected $cache; + + /** + * remote connector + * + * @var \Federator\Connector\Connector $connector + */ + protected $connector = null; + + /** + * database instance + * + * @var \Mysqli $dbh + */ + protected $dbh; + + /** + * Set up environment for this job + */ + public function setUp(): void + { + $contentnation = new \Federator\Api(); + $contentnation->openDatabase(); + $contentnation->loadPlugins(); + + // Recreate dependencies as needed + $this->dbh = $contentnation->getDatabase(); // get DB connection + $this->connector = $contentnation->getConnector(); // get connector + $this->cache = $contentnation->getCache(); // get cache + } + + /** + * Perform the inbox job. + * + * @return bool true on success, false on failure + */ + public function perform(): bool + { + error_log("InboxJob: Starting inbox job"); + $user = $this->args['user']; + $activity = $this->args['activity']; + + $inboxActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($activity); + + if ($inboxActivity === false) { + error_log("InboxJob: Failed to create inboxActivity from JSON"); + return false; + } + + \Federator\Api\FedUsers\Inbox::postForUser($this->dbh, $this->connector, $this->cache, $user, $inboxActivity); + return true; + } +} \ No newline at end of file diff --git a/php/federator/language.php b/php/federator/language.php index 2e58133..699746f 100644 --- a/php/federator/language.php +++ b/php/federator/language.php @@ -92,12 +92,9 @@ class Language } if (! isset($this->lang[$group])) { $l = []; - $root = $_SERVER['DOCUMENT_ROOT']; - if ($root === '') { - $root = '.'; - } - if (@file_exists($root . '../lang/federator/' . $this->uselang . "/$group.inc")) { - require($root . '../lang/federator/' . $this->uselang . "/$group.inc"); + $root = PROJECT_ROOT; + if (@file_exists($root . '/lang/federator/' . $this->uselang . "/$group.inc")) { + require($root . '/lang/federator/' . $this->uselang . "/$group.inc"); $this->lang[$group] = $l; } } @@ -112,7 +109,7 @@ class Language } return $string; } - $basedir = $_SERVER['DOCUMENT_ROOT'] . '/../'; + $basedir = PROJECT_ROOT; $fh = @fopen("$basedir/logs/missingtrans.txt", 'a'); if ($fh !== false) { fwrite($fh, $this->uselang.":$group:$key\n"); @@ -132,7 +129,7 @@ class Language { if (! isset($this->lang[$group])) { $l = []; - require_once($_SERVER['DOCUMENT_ROOT'] . '/../lang/' . $this->uselang . "/$group.inc"); + require_once(PROJECT_ROOT . '/lang/' . $this->uselang . "/$group.inc"); $this->lang[$group] = $l; } // @phan-suppress-next-line PhanPartialTypeMismatchReturn diff --git a/php/federator/main.php b/php/federator/main.php index f63f8ee..fb77f3f 100644 --- a/php/federator/main.php +++ b/php/federator/main.php @@ -78,9 +78,9 @@ class Main */ public function __construct() { - require_once($_SERVER['DOCUMENT_ROOT'] . '../vendor/autoload.php'); + require_once(PROJECT_ROOT . '/vendor/autoload.php'); $this->responseCode = 200; - $rootDir = $_SERVER['DOCUMENT_ROOT'] . '../'; + $rootDir = PROJECT_ROOT . '/'; $config = parse_ini_file($rootDir . 'config.ini', true); if ($config !== false) { $this->config = $config; @@ -189,7 +189,7 @@ class Main public function loadPlugins(): void { if (array_key_exists('plugins', $this->config)) { - $basepath = $_SERVER['DOCUMENT_ROOT'] . '../plugins/federator/'; + $basepath = PROJECT_ROOT . '/plugins/federator/'; $plugins = $this->config['plugins']; foreach ($plugins as $name => $file) { require_once($basepath . $file); @@ -233,9 +233,8 @@ class Main public function renderTemplate($template, $data) { $smarty = new \Smarty\Smarty(); - $root = $_SERVER['DOCUMENT_ROOT']; - $smarty->setCompileDir($root . $this->config['templates']['compiledir']); - $smarty->setTemplateDir((string) realpath($root . $this->config['templates']['path'])); + $smarty->setCompileDir(PROJECT_ROOT . $this->config['templates']['compiledir']); + $smarty->setTemplateDir((string) realpath(PROJECT_ROOT . $this->config['templates']['path'])); $smarty->assign('database', $this->dbh); $smarty->assign('maininstance', $this); foreach ($data as $key => $value) { diff --git a/php/federator/maintenance.php b/php/federator/maintenance.php index 4177498..cd9c03a 100644 --- a/php/federator/maintenance.php +++ b/php/federator/maintenance.php @@ -24,14 +24,13 @@ class Maintenance { date_default_timezone_set("Europe/Berlin"); spl_autoload_register(static function (string $className) { - $root = $_SERVER['DOCUMENT_ROOT']; - include $root . '../php/' . str_replace("\\", "/", strtolower($className)) . '.php'; + include PROJECT_ROOT . '/php/' . str_replace("\\", "/", strtolower($className)) . '.php'; }); if ($argc < 2) { self::printUsage(); } // pretend that we are running from web directory - $_SERVER['DOCUMENT_ROOT'] = realpath('../../htdocs') . '/'; + define('PROJECT_ROOT', dirname(__DIR__, 2)); $main = new \Federator\Main(); switch ($argv[1]) { case 'dbupgrade': @@ -71,7 +70,7 @@ class Maintenance } } echo "current version: $version\n"; - $root = $_SERVER['DOCUMENT_ROOT'] . '../'; + $root = PROJECT_ROOT . '/'; $updateFolder = opendir($root . 'sql'); if ($updateFolder === false) { die(); diff --git a/php/federator/workers/worker_inbox.php b/php/federator/workers/worker_inbox.php new file mode 100644 index 0000000..4eb3646 --- /dev/null +++ b/php/federator/workers/worker_inbox.php @@ -0,0 +1,11 @@ +work(10); // 10 seconds interval \ No newline at end of file diff --git a/plugins/federator/contentnation.php b/plugins/federator/contentnation.php index fd9bf43..9e8cda8 100644 --- a/plugins/federator/contentnation.php +++ b/plugins/federator/contentnation.php @@ -41,7 +41,7 @@ class ContentNation implements Connector */ public function __construct($main) { - $config = parse_ini_file($_SERVER['DOCUMENT_ROOT'] . '../contentnation.ini', true); + $config = parse_ini_file(PROJECT_ROOT . '/contentnation.ini', true); if ($config !== false) { $this->config = $config; } @@ -424,7 +424,7 @@ class ContentNation implements Connector $signature = base64_decode($signatureParts['signature']); $signedHeaders = explode(' ', $signatureParts['headers']); - $pKeyPath = $_SERVER['DOCUMENT_ROOT'] . $this->config['keys']['publicKeyPath']; + $pKeyPath = PROJECT_ROOT . '/' . $this->config['keys']['publicKeyPath']; $publicKeyPem = file_get_contents($pKeyPath); if ($publicKeyPem === false) { http_response_code(500); diff --git a/plugins/federator/rediscache.php b/plugins/federator/rediscache.php index dc466a1..17b8deb 100644 --- a/plugins/federator/rediscache.php +++ b/plugins/federator/rediscache.php @@ -53,7 +53,7 @@ class RedisCache implements Cache */ public function __construct() { - $config = parse_ini_file('../rediscache.ini'); + $config = parse_ini_file(PROJECT_ROOT . '/rediscache.ini'); if ($config !== false) { $this->config = $config; $this->userTTL = array_key_exists('userttl', $config) ? intval($config['userttl'], 10) : 60; @@ -67,10 +67,10 @@ class RedisCache implements Cache */ private function connect() { - $this->redis = new \Redis(); - $this->redis->pconnect($this->config['host'], intval($this->config['port'], 10)); - // @phan-suppress-next-line PhanTypeMismatchArgumentInternalProbablyReal - $this->redis->auth([$this->config['username'], $this->config['password']]); + $this->redis = new \Redis(); + $this->redis->pconnect($this->config['host'], intval($this->config['port'], 10)); + // @phan-suppress-next-line PhanTypeMismatchArgumentInternalProbablyReal + $this->redis->auth([$this->config['username'], $this->config['password']]); } /**