support for active fetching of user info

develop
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) { if (preg_match("/^acct:([^@]+)@(.*)$/", $_resource, $matches) != 1 || $matches[2] !== $domain) {
throw new \Federator\Exceptions\InvalidArgument(); 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) { if ($user->id == 0) {
throw new \Federator\Exceptions\FileNotFound(); throw new \Federator\Exceptions\FileNotFound();
} }

View File

@ -13,6 +13,15 @@ namespace Federator\Cache;
*/ */
interface Cache extends \Federator\Connector\Connector 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 * save remote user by given session
* *

View File

@ -13,6 +13,14 @@ namespace Federator\Connector;
*/ */
interface 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 * get remote user by given session
* *

View File

@ -22,9 +22,7 @@ class User
*/ */
protected static function addLocalUser($dbh, $user, $_user) protected static function addLocalUser($dbh, $user, $_user)
{ {
echo "a new user\n";
// needed fields: RSA key pair, user name (handle) // needed fields: RSA key pair, user name (handle)
$private_key = openssl_pkey_new(); $private_key = openssl_pkey_new();
if ($private_key === false) { if ($private_key === false) {
throw new \Federator\Exceptions\ServerError(); throw new \Federator\Exceptions\ServerError();
@ -32,7 +30,9 @@ class User
$public = openssl_pkey_get_details($private_key)['key']; $public = openssl_pkey_get_details($private_key)['key'];
$private = ''; $private = '';
openssl_pkey_export($private_key, $private); openssl_pkey_export($private_key, $private);
$sql = 'insert into users (id, externalid, rsapublic, rsaprivate) values (?, ?, ?, ?)'; 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); $stmt = $dbh->prepare($sql);
if ($stmt === false) { if ($stmt === false) {
throw new \Federator\Exceptions\ServerError(); throw new \Federator\Exceptions\ServerError();
@ -40,6 +40,10 @@ class User
$stmt->bind_param("ssss", $_user, $user->externalid, $public, $private); $stmt->bind_param("ssss", $_user, $user->externalid, $public, $private);
$stmt->execute(); $stmt->execute();
$stmt->close(); $stmt->close();
$user->id = $_user;
} catch (\mysqli_sql_exception $e) {
error_log($e->getMessage());
}
} }
/** /**
@ -77,23 +81,50 @@ class User
* database handle * database handle
* @param string $_name * @param string $_name
* user 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 * @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); $stmt = $dbh->prepare($sql);
if ($stmt === false) { if ($stmt === false) {
throw new \Federator\Exceptions\ServerError(); throw new \Federator\Exceptions\ServerError();
} }
$stmt->bind_param("s", $_name); $stmt->bind_param("s", $_name);
$user = new \Federator\Data\User(); $user = new \Federator\Data\User();
$ret = $stmt->bind_result($user->id); $ret = $stmt->bind_result($user->id, $user->externalid);
$stmt->execute(); $stmt->execute();
if ($ret) { if ($ret) {
$stmt->fetch(); $stmt->fetch();
} }
$stmt->close(); $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; return $user;
} }

View File

@ -9,8 +9,6 @@
namespace Federator; namespace Federator;
require_once($_SERVER['DOCUMENT_ROOT'] . '../vendor/autoload.php');
/** /**
* Base class for Api and related classes * Base class for Api and related classes
* @author Sascha Nitsch * @author Sascha Nitsch
@ -74,6 +72,7 @@ class Main
*/ */
public function __construct() public function __construct()
{ {
require_once($_SERVER['DOCUMENT_ROOT'] . '../vendor/autoload.php');
$this->responseCode = 200; $this->responseCode = 200;
$rootDir = $_SERVER['DOCUMENT_ROOT'] . '../'; $rootDir = $_SERVER['DOCUMENT_ROOT'] . '../';
$config = parse_ini_file($rootDir . 'config.ini', true); $config = parse_ini_file($rootDir . 'config.ini', true);
@ -131,6 +130,26 @@ class Main
return [$ret, $info]; 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 * get config
* @return Array<String, Mixed> * @return Array<String, Mixed>

View File

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

View File

@ -13,6 +13,13 @@
*/ */
class ContentNation implements Connector class ContentNation implements Connector
{ {
/**
* config parameter
*
* @var array<string, mixed> $config
*/
private $config;
/** /**
* service-URL * service-URL
* *
@ -23,13 +30,43 @@ class ContentNation implements Connector
/** /**
* constructor * 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']; $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 * get remote user by given session
* *
@ -78,6 +115,6 @@ namespace Federator;
*/ */
function contentnation_load($main) function contentnation_load($main)
{ {
$cn = new Connector\ContentNation($main->getConfig()['contentnation']); $cn = new Connector\ContentNation();
$main->setConnector($cn); $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 * get remote user by given session
* @param string $_session session id * @param string $_session session id

View File

@ -69,16 +69,34 @@ class RedisCache implements Cache
* create key from session and user * create key from session and user
* *
* @param string $prefix prefix to create name spaces * @param string $prefix prefix to create name spaces
* @param string $_session session id * @param string $input key name
* @param string $_user user/profile name
* @return string key * @return string key
*/ */
private static function createKey($prefix, $_session, $_user) private static function createKey($prefix, $input)
{ {
$key = $prefix . '_'; return $prefix . '_' . md5($input);
$key .= md5($_session . $_user);
return $key;
} }
/**
* 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 * get remote user by given session
* *
@ -91,7 +109,7 @@ class RedisCache implements Cache
if (!$this->connected) { if (!$this->connected) {
$this->connect(); $this->connect();
} }
$key = self::createKey('u', $_session, $_user); $key = self::createKey('s', $_session . $_user);
$data = $this->redis->get($key); $data = $this->redis->get($key);
if ($data === false) { if ($data === false) {
return false; return false;
@ -100,6 +118,20 @@ class RedisCache implements Cache
return $user; 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 * save remote user by given session
* *
@ -110,7 +142,7 @@ class RedisCache implements Cache
*/ */
public function saveRemoteUserBySession($_session, $_user, $user) public function saveRemoteUserBySession($_session, $_user, $user)
{ {
$key = self::createKey('u', $_session, $_user); $key = self::createKey('s', $_session . $_user);
$serialized = $user->toJson(); $serialized = $user->toJson();
$this->redis->setEx($key, $this->userTTL, $serialized,); $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";