support for active fetching of user info

This commit is contained in:
Sascha Nitsch 2024-07-21 17:02:39 +02:00
parent 47efd74b6c
commit 63532c54ea
11 changed files with 188 additions and 27 deletions

2
contentnation.ini Normal file
View file

@ -0,0 +1,2 @@
[contentnation]
service-uri = http://local.contentnation.net

View file

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

View file

@ -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
*

View file

@ -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
*

View file

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

View file

@ -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<String, Mixed>

View file

@ -112,7 +112,8 @@ class Maintenance
{
echo "usage php maintenance.php <command>\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();
}
}

View file

@ -13,6 +13,13 @@
*/
class ContentNation implements Connector
{
/**
* config parameter
*
* @var array<string, mixed> $config
*/
private $config;
/**
* service-URL
*
@ -23,13 +30,43 @@ class ContentNation implements Connector
/**
* constructor
*
* @param array<string, mixed> $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);
}

View file

@ -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

View file

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

2
sql/2024-07-21.sql Normal file
View file

@ -0,0 +1,2 @@
alter table users add `validuntil` timestamp default 0;
update settings set `value`="2024-07-21" where `key`="database_version";