forked from grumpydevelop/federator

- integrate support to send new posts to CN - save original article-id in DB (needs db-migration) - votes and comments on CN-articles and comments are sent to CN, with proper signing and format - fixed minor issue where delete-activity was not properly working with objects - fixed minor issue where tombstone wasn't supported (which prevented being able to delete mastodon-posts from the db)
395 lines
11 KiB
PHP
395 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* @author Sascha Nitsch <grumpydevelop@contentnation.net>
|
|
**/
|
|
|
|
namespace Federator\Cache;
|
|
|
|
/**
|
|
* Caching class using redis
|
|
*/
|
|
class RedisCache implements Cache
|
|
{
|
|
/**
|
|
* config data
|
|
*
|
|
* @var array<string, mixed> $config
|
|
*/
|
|
private $config;
|
|
|
|
/**
|
|
* connection to redis open flag
|
|
*
|
|
* @var bool $connected
|
|
*/
|
|
private $connected = false;
|
|
|
|
/**
|
|
* connection handle
|
|
*
|
|
* @var \Redis $redis
|
|
*/
|
|
private $redis;
|
|
|
|
/**
|
|
* user cache time to live in secods
|
|
*
|
|
* @var int $userTTL
|
|
*/
|
|
private $userTTL;
|
|
|
|
/**
|
|
* public key cache time to live in secods
|
|
*
|
|
* @var int $publicKeyPemTTL
|
|
*/
|
|
private $publicKeyPemTTL;
|
|
|
|
/**
|
|
* constructor
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$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;
|
|
$this->publicKeyPemTTL = array_key_exists('publickeypemttl', $config) ? intval($config['publickeypemttl'], 10) : 3600;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* connect to redis
|
|
* @return void
|
|
*/
|
|
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']]);
|
|
|
|
// Set the Redis backend for Resque
|
|
$redisUrl = sprintf(
|
|
'redis://%s:%s@%s:%d',
|
|
urlencode($this->config['username']),
|
|
urlencode($this->config['password']),
|
|
$this->config['host'],
|
|
intval($this->config['port'], 10)
|
|
);
|
|
\Resque::setBackend($redisUrl);
|
|
}
|
|
|
|
/**
|
|
* create key from session and user
|
|
*
|
|
* @param string $prefix prefix to create name spaces
|
|
* @param string $input key name
|
|
* @return string key
|
|
*/
|
|
private static function createKey($prefix, $input)
|
|
{
|
|
return $prefix . '_' . md5($input);
|
|
}
|
|
|
|
/**
|
|
* get followers of given user
|
|
*
|
|
* @param string $id user id @unused-param
|
|
|
|
* @return \Federator\Data\FedUser[]|false
|
|
*/
|
|
public function getRemoteFollowersOfUser($id)
|
|
{
|
|
error_log("rediscache::getRemoteFollowersOfUser not implemented");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* get following of given user
|
|
*
|
|
* @param string $id user id @unused-param
|
|
|
|
* @return \Federator\Data\FedUser[]|false
|
|
*/
|
|
public function getRemoteFollowingForUser($id)
|
|
{
|
|
error_log("rediscache::getRemoteFollowingForUser not implemented");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Convert jsonData to Activity format
|
|
*
|
|
* @param array<string, mixed> $jsonData the json data from our platfrom @unused-param
|
|
* @param string $articleId the original id of the article (if applicable)
|
|
* (used to identify the article in the remote system) @unused-param
|
|
* @return \Federator\Data\ActivityPub\Common\Activity|false
|
|
*/
|
|
public function jsonToActivity(array $jsonData, &$articleId)
|
|
{
|
|
error_log("rediscache::jsonToActivity not implemented");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* get posts by given user
|
|
*
|
|
* @param string $id user id @unused-param
|
|
* @param string $min min date @unused-param
|
|
* @param string $max max date @unused-param
|
|
|
|
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
|
*/
|
|
public function getRemotePostsByUser($id, $min, $max)
|
|
{
|
|
error_log("rediscache::getRemotePostsByUser not implemented");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* get statistics from remote system
|
|
*
|
|
* @return \Federator\Data\Stats|false
|
|
*/
|
|
public function getRemoteStats()
|
|
{
|
|
if (!$this->connected) {
|
|
$this->connect();
|
|
}
|
|
$key = 'm_stats';
|
|
$data = $this->redis->get($key);
|
|
if ($data === false) {
|
|
return false;
|
|
}
|
|
$stats = \Federator\Data\Stats::createFromJson($data);
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* get remote user by given name
|
|
*
|
|
* @param string $_name user/profile name
|
|
* @return \Federator\Data\User | false
|
|
*/
|
|
public function getRemoteUserByName(string $_name)
|
|
{
|
|
if (!$this->connected) {
|
|
$this->connect();
|
|
}
|
|
$key = self::createKey('u', $_name);
|
|
$data = $this->redis->get($key);
|
|
if ($data === false) {
|
|
return false;
|
|
}
|
|
$user = \Federator\Data\User::createFromJson($data);
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* get remote federation user by given name
|
|
*
|
|
* @param string $_name user/profile name
|
|
* @return \Federator\Data\FedUser | false
|
|
*/
|
|
public function getRemoteFedUserByName(string $_name)
|
|
{
|
|
if (!$this->connected) {
|
|
$this->connect();
|
|
}
|
|
$key = self::createKey('u', $_name);
|
|
$data = $this->redis->get($key);
|
|
if ($data === false) {
|
|
return false;
|
|
}
|
|
$user = \Federator\Data\FedUser::createFromJson($data);
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* get remote user by given session
|
|
*
|
|
* @param string $_session session id
|
|
* @param string $_user user/profile name
|
|
* @return \Federator\Data\User | false
|
|
*/
|
|
public function getRemoteUserBySession($_session, $_user)
|
|
{
|
|
if (!$this->connected) {
|
|
$this->connect();
|
|
}
|
|
$key = self::createKey('s', $_session . $_user);
|
|
$data = $this->redis->get($key);
|
|
if ($data === false) {
|
|
return false;
|
|
}
|
|
$user = \Federator\Data\User::createFromJson($data);
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the public key for a given keyId
|
|
*
|
|
* @param string $keyId The keyId (e.g., actor URL + #main-key)
|
|
* @return string|false The cached public key PEM or false if not found
|
|
*/
|
|
public function getPublicKey(string $keyId)
|
|
{
|
|
if (!$this->connected) {
|
|
$this->connect();
|
|
}
|
|
$key = self::createKey('publickey', $keyId);
|
|
return $this->redis->get($key);
|
|
}
|
|
|
|
/**
|
|
* save remote followers by user
|
|
*
|
|
* @param string $user user name @unused-param
|
|
* @param \Federator\Data\FedUser[]|false $followers user followers @unused-param
|
|
* @return void
|
|
*/
|
|
public function saveRemoteFollowersOfUser($user, $followers)
|
|
{
|
|
error_log("rediscache::saveRemoteFollowersOfUser not implemented");
|
|
}
|
|
|
|
/**
|
|
* save remote following for user
|
|
*
|
|
* @param string $user user name @unused-param
|
|
* @param \Federator\Data\FedUser[]|false $following user following @unused-param
|
|
* @return void
|
|
*/
|
|
public function saveRemoteFollowingForUser($user, $following)
|
|
{
|
|
error_log("rediscache::saveRemoteFollowingForUser not implemented");
|
|
}
|
|
|
|
/**
|
|
* save remote posts by user
|
|
*
|
|
* @param string $user user name @unused-param
|
|
* @param \Federator\Data\ActivityPub\Common\APObject[]|false $posts user posts @unused-param
|
|
* @return void
|
|
*/
|
|
public function saveRemotePostsByUser($user, $posts)
|
|
{
|
|
error_log("rediscache::saveRemotePostsByUser not implemented");
|
|
}
|
|
|
|
/**
|
|
* save remote stats
|
|
*
|
|
* @param \Federator\Data\Stats $stats stats to save
|
|
* @return void
|
|
*/
|
|
public function saveRemoteStats($stats)
|
|
{
|
|
if (!$this->connected) {
|
|
$this->connect();
|
|
}
|
|
$key = 'm_stats';
|
|
$serialized = $stats->toJson();
|
|
$this->redis->setEx($key, $this->config['statsttl'], $serialized);
|
|
}
|
|
/**
|
|
* save remote user by name
|
|
*
|
|
* @param string $_name user/profile name
|
|
* @param \Federator\Data\User $user user data
|
|
* @return void
|
|
*/
|
|
public function saveRemoteUserByName($_name, $user)
|
|
{
|
|
$key = self::createKey('u', $_name);
|
|
$serialized = $user->toJson();
|
|
$this->redis->setEx($key, $this->userTTL, $serialized);
|
|
}
|
|
|
|
/**
|
|
* save remote federation user by given name
|
|
*
|
|
* @param string $_name user/profile name
|
|
* @param \Federator\Data\FedUser $user user data
|
|
* @return void
|
|
*/
|
|
public function saveRemoteFedUserByName(string $_name, \Federator\Data\FedUser $user)
|
|
{
|
|
$key = self::createKey('u', $_name);
|
|
$serialized = $user->toJson();
|
|
$this->redis->setEx($key, $this->userTTL, $serialized);
|
|
}
|
|
|
|
/**
|
|
* save remote user by given session
|
|
*
|
|
* @param string $_session session id
|
|
* @param string $_user user/profile name
|
|
* @param \Federator\Data\User $user user data
|
|
* @return void
|
|
*/
|
|
public function saveRemoteUserBySession($_session, $_user, $user)
|
|
{
|
|
$key = self::createKey('s', $_session . $_user);
|
|
$serialized = $user->toJson();
|
|
$this->redis->setEx($key, $this->userTTL, $serialized);
|
|
}
|
|
|
|
/**
|
|
* Save the public key for a given keyId
|
|
*
|
|
* @param string $keyId The keyId (e.g., actor URL + #main-key)
|
|
* @param string $publicKeyPem The public key PEM to cache
|
|
* @return void
|
|
*/
|
|
public function savePublicKey(string $keyId, string $publicKeyPem)
|
|
{
|
|
if (!$this->connected) {
|
|
$this->connect();
|
|
}
|
|
$key = self::createKey('publickey', $keyId);
|
|
$this->redis->setEx($key, $this->publicKeyPemTTL, $publicKeyPem);
|
|
}
|
|
|
|
/**
|
|
* send target-friendly json from ActivityPub activity
|
|
*
|
|
* @param \Federator\Data\FedUser $sender the user of the sender @unused-param
|
|
* @param \Federator\Data\ActivityPub\Common\Activity $activity the activity @unused-param
|
|
* @return boolean did we successfully send the activity?
|
|
*/
|
|
public function sendActivity($sender, $activity)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* check if the headers include a valid signature
|
|
*
|
|
* @param string[] $headers the headers @unused-param
|
|
* @throws \Federator\Exceptions\PermissionDenied
|
|
* @return string|\Federator\Exceptions\PermissionDenied
|
|
*/
|
|
public function checkSignature($headers)
|
|
{
|
|
return new \Federator\Exceptions\PermissionDenied("RedisCache: no signature check");
|
|
}
|
|
}
|
|
|
|
namespace Federator;
|
|
|
|
/**
|
|
* Function to initialize plugin
|
|
*
|
|
* @param \Federator\Main $main main instance
|
|
* @return void
|
|
*/
|
|
function rediscache_load($main)
|
|
{
|
|
$rc = new Cache\RedisCache();
|
|
$main->setCache($rc);
|
|
}
|