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