From 63532c54ea10200749920e47b655e2efcdf2b509 Mon Sep 17 00:00:00 2001 From: Sascha Nitsch Date: Sun, 21 Jul 2024 17:02:39 +0200 Subject: [PATCH] support for active fetching of user info --- contentnation.ini | 2 + php/federator/api/wellknown/webfinger.php | 7 ++- php/federator/cache/cache.php | 9 ++++ php/federator/connector/connector.php | 8 ++++ php/federator/dio/user.php | 55 ++++++++++++++++++----- php/federator/main.php | 23 +++++++++- php/federator/maintenance.php | 3 +- plugins/federator/contentnation.php | 43 ++++++++++++++++-- plugins/federator/dummyconnector.php | 15 +++++++ plugins/federator/rediscache.php | 48 ++++++++++++++++---- sql/2024-07-21.sql | 2 + 11 files changed, 188 insertions(+), 27 deletions(-) create mode 100644 contentnation.ini create mode 100644 sql/2024-07-21.sql diff --git a/contentnation.ini b/contentnation.ini new file mode 100644 index 0000000..140802f --- /dev/null +++ b/contentnation.ini @@ -0,0 +1,2 @@ +[contentnation] +service-uri = http://local.contentnation.net diff --git a/php/federator/api/wellknown/webfinger.php b/php/federator/api/wellknown/webfinger.php index 8c1cc16..72555e3 100644 --- a/php/federator/api/wellknown/webfinger.php +++ b/php/federator/api/wellknown/webfinger.php @@ -51,7 +51,12 @@ class WebFinger if (preg_match("/^acct:([^@]+)@(.*)$/", $_resource, $matches) != 1 || $matches[2] !== $domain) { throw new \Federator\Exceptions\InvalidArgument(); } - $user = \Federator\DIO\User::getUserByName($this->main->getDatabase(), $matches[1]); + $user = \Federator\DIO\User::getUserByName( + $this->main->getDatabase(), + $matches[1], + $this->main->getConnector(), + $this->main->getCache() + ); if ($user->id == 0) { throw new \Federator\Exceptions\FileNotFound(); } diff --git a/php/federator/cache/cache.php b/php/federator/cache/cache.php index 0cd9c2b..2678705 100644 --- a/php/federator/cache/cache.php +++ b/php/federator/cache/cache.php @@ -13,6 +13,15 @@ namespace Federator\Cache; */ interface Cache extends \Federator\Connector\Connector { + /** + * save remote user by given name + * + * @param string $_name user/profile name + * @param \Federator\Data\User $user user data + * @return void + */ + public function saveRemoteUserByName($_name, $user); + /** * save remote user by given session * diff --git a/php/federator/connector/connector.php b/php/federator/connector/connector.php index 0b7dbd3..17474e5 100644 --- a/php/federator/connector/connector.php +++ b/php/federator/connector/connector.php @@ -13,6 +13,14 @@ namespace Federator\Connector; */ interface Connector { + /** + * get remote user by given name + * + * @param string $_name user/profile name + * @return \Federator\Data\User | false + */ + public function getRemoteUserByName(string $_name); + /** * get remote user by given session * diff --git a/php/federator/dio/user.php b/php/federator/dio/user.php index 65fde70..ba77d19 100644 --- a/php/federator/dio/user.php +++ b/php/federator/dio/user.php @@ -22,9 +22,7 @@ class User */ protected static function addLocalUser($dbh, $user, $_user) { - echo "a new user\n"; // needed fields: RSA key pair, user name (handle) - $private_key = openssl_pkey_new(); if ($private_key === false) { throw new \Federator\Exceptions\ServerError(); @@ -32,14 +30,20 @@ class User $public = openssl_pkey_get_details($private_key)['key']; $private = ''; openssl_pkey_export($private_key, $private); - $sql = 'insert into users (id, externalid, rsapublic, rsaprivate) values (?, ?, ?, ?)'; - $stmt = $dbh->prepare($sql); - if ($stmt === false) { - throw new \Federator\Exceptions\ServerError(); + try { + $sql = 'insert into users (id, externalid, rsapublic, rsaprivate, validuntil)'; + $sql .= ' values (?, ?, ?, ?, now() + interval 1 day) on duplicate key update validuntil=now() + interval 1 day'; + $stmt = $dbh->prepare($sql); + if ($stmt === false) { + throw new \Federator\Exceptions\ServerError(); + } + $stmt->bind_param("ssss", $_user, $user->externalid, $public, $private); + $stmt->execute(); + $stmt->close(); + $user->id = $_user; + } catch (\mysqli_sql_exception $e) { + error_log($e->getMessage()); } - $stmt->bind_param("ssss", $_user, $user->externalid, $public, $private); - $stmt->execute(); - $stmt->close(); } /** @@ -77,23 +81,50 @@ class User * database handle * @param string $_name * user name + * @param \Federator\Connector\Connector $connector + * connector to fetch use with + * @param \Federator\Cache\Cache|null $cache + * optional caching service * @return \Federator\Data\User */ - public static function getUserByName($dbh, $_name) + public static function getUserByName($dbh, $_name, $connector, $cache) { - $sql = 'select id from users where id=?'; + $user = false; + // ask cache + if ($cache !== null) { + $user = $cache->getRemoteUserByName($_name); + } + if ($user !== false) { + return $user; + } + // check our db + $sql = 'select id,externalid from users where id=? and validuntil>=now()'; $stmt = $dbh->prepare($sql); if ($stmt === false) { throw new \Federator\Exceptions\ServerError(); } $stmt->bind_param("s", $_name); $user = new \Federator\Data\User(); - $ret = $stmt->bind_result($user->id); + $ret = $stmt->bind_result($user->id, $user->externalid); $stmt->execute(); if ($ret) { $stmt->fetch(); } $stmt->close(); + if ($user->id === null) { + // ask connector for user-id + $ruser = $connector->getRemoteUserByName($_name); + if ($ruser !== false) { + $user = $ruser; + } + } + if ($cache !== null) { + print_r($user); + if ($user->id === null) { + self::addLocalUser($dbh, $user, $_name); + } + $cache->saveRemoteUserByName($_name, $user); + } return $user; } diff --git a/php/federator/main.php b/php/federator/main.php index ffb6388..456c57f 100644 --- a/php/federator/main.php +++ b/php/federator/main.php @@ -9,8 +9,6 @@ namespace Federator; -require_once($_SERVER['DOCUMENT_ROOT'] . '../vendor/autoload.php'); - /** * Base class for Api and related classes * @author Sascha Nitsch @@ -74,6 +72,7 @@ class Main */ public function __construct() { + require_once($_SERVER['DOCUMENT_ROOT'] . '../vendor/autoload.php'); $this->responseCode = 200; $rootDir = $_SERVER['DOCUMENT_ROOT'] . '../'; $config = parse_ini_file($rootDir . 'config.ini', true); @@ -131,6 +130,26 @@ class Main return [$ret, $info]; } + /** + * get cache + * + * @return \Federator\Cache\Cache + */ + public function getCache() + { + return $this->cache; + } + + /** + * get connector + * + * @return \Federator\Connector\Connector + */ + public function getConnector() + { + return $this->connector; + } + /** * get config * @return Array diff --git a/php/federator/maintenance.php b/php/federator/maintenance.php index e518f2c..950eebd 100644 --- a/php/federator/maintenance.php +++ b/php/federator/maintenance.php @@ -112,7 +112,8 @@ class Maintenance { echo "usage php maintenance.php \n"; echo "command can be one of:\n"; - echo " dbupgrade - this upgrades the db to the most recent version. Run this after you updated the program files\n"; + echo " dbupgrade - this upgrades the db to the most recent version.\n"; + echo " Run this after you updated the program files\n"; exit(); } } diff --git a/plugins/federator/contentnation.php b/plugins/federator/contentnation.php index ca7a5cd..1e34b19 100644 --- a/plugins/federator/contentnation.php +++ b/plugins/federator/contentnation.php @@ -13,6 +13,13 @@ */ class ContentNation implements Connector { + /** + * config parameter + * + * @var array $config + */ + private $config; + /** * service-URL * @@ -23,13 +30,43 @@ class ContentNation implements Connector /** * constructor * - * @param array $config */ - public function __construct($config) + public function __construct() { + $config = parse_ini_file($_SERVER['DOCUMENT_ROOT'] . '../contentnation.ini'); $this->service = $config['service-uri']; } + /** + * get remote user by given name + * + * @param string $_name user/profile name + * @return \Federator\Data\User | false + */ + public function getRemoteUserByName(string $_name) + { + // validate name + if (preg_match("/^[a-zA-Z0-9_\-]+$/", $_name) != 1) { + return false; + } + $remoteURL = $this->service . '/api/users/info?user=' . urlencode($_name); + $headers = ['Accept: application/json']; + [$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers); + if ($info['http_code'] != 200) { + return false; + } + $r = json_decode($response, true); + if ($r === false || $r === null || !is_array($r)) { + return false; + } + if (!array_key_exists('name', $r) || $r['name'] !== $_name) { + return false; + } + $user = new \Federator\Data\User(); + $user->externalid = $_name; + return $user; + } + /** * get remote user by given session * @@ -78,6 +115,6 @@ namespace Federator; */ function contentnation_load($main) { - $cn = new Connector\ContentNation($main->getConfig()['contentnation']); + $cn = new Connector\ContentNation(); $main->setConnector($cn); } diff --git a/plugins/federator/dummyconnector.php b/plugins/federator/dummyconnector.php index eee527c..7abb589 100644 --- a/plugins/federator/dummyconnector.php +++ b/plugins/federator/dummyconnector.php @@ -19,6 +19,21 @@ class DummyConnector implements Connector { } + /** + * get remote user by name + * @param string $_name user or profile name + * @return \Federator\Data\User | false + */ + public function getRemoteUserByName(string $_name) + { + // validate $_session and $user + $user = new \Federator\Data\User(); + $user->externalid = $_name; + $user->permissions = []; + $user->session = ''; + return $user; + } + /** * get remote user by given session * @param string $_session session id diff --git a/plugins/federator/rediscache.php b/plugins/federator/rediscache.php index fccdc75..bec8b19 100644 --- a/plugins/federator/rediscache.php +++ b/plugins/federator/rediscache.php @@ -69,16 +69,34 @@ class RedisCache implements Cache * create key from session and user * * @param string $prefix prefix to create name spaces - * @param string $_session session id - * @param string $_user user/profile name + * @param string $input key name * @return string key */ - private static function createKey($prefix, $_session, $_user) + private static function createKey($prefix, $input) { - $key = $prefix . '_'; - $key .= md5($_session . $_user); - return $key; + return $prefix . '_' . md5($input); } + + /** + * 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 user by given session * @@ -91,7 +109,7 @@ class RedisCache implements Cache if (!$this->connected) { $this->connect(); } - $key = self::createKey('u', $_session, $_user); + $key = self::createKey('s', $_session . $_user); $data = $this->redis->get($key); if ($data === false) { return false; @@ -100,6 +118,20 @@ class RedisCache implements Cache return $user; } + /** + * save remote user by namr + * + * @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 user by given session * @@ -110,7 +142,7 @@ class RedisCache implements Cache */ public function saveRemoteUserBySession($_session, $_user, $user) { - $key = self::createKey('u', $_session, $_user); + $key = self::createKey('s', $_session . $_user); $serialized = $user->toJson(); $this->redis->setEx($key, $this->userTTL, $serialized,); } diff --git a/sql/2024-07-21.sql b/sql/2024-07-21.sql new file mode 100644 index 0000000..054e354 --- /dev/null +++ b/sql/2024-07-21.sql @@ -0,0 +1,2 @@ +alter table users add `validuntil` timestamp default 0; +update settings set `value`="2024-07-21" where `key`="database_version"; \ No newline at end of file