Add VariableManager, RpcClient classes, and cache enhancements
This commit is contained in:
parent
38092a639e
commit
e55f4d57f9
27 changed files with 606 additions and 56 deletions
14
.idea/php-test-framework.xml
generated
14
.idea/php-test-framework.xml
generated
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PhpTestFrameworkVersionCache">
|
||||
<tools_cache>
|
||||
<tool tool_name="PHPUnit">
|
||||
<cache>
|
||||
<versions>
|
||||
<info id="Local/home/netkas/phpunit.phar" version="11.3.5" />
|
||||
</versions>
|
||||
</cache>
|
||||
</tool>
|
||||
</tools_cache>
|
||||
</component>
|
||||
</project>
|
2
.idea/php.xml
generated
2
.idea/php.xml
generated
|
@ -69,7 +69,7 @@
|
|||
<extension name="libevent" enabled="false" />
|
||||
<extension name="libsodium" enabled="false" />
|
||||
<extension name="mailparse" enabled="false" />
|
||||
<extension name="memcached" enabled="false" />
|
||||
<extension name="memcache" enabled="false" />
|
||||
<extension name="ming" enabled="false" />
|
||||
<extension name="mongo" enabled="false" />
|
||||
<extension name="mongodb" enabled="false" />
|
||||
|
|
3
.idea/sqldialects.xml
generated
3
.idea/sqldialects.xml
generated
|
@ -4,6 +4,9 @@
|
|||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/password_authentication.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/registered_peers.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/sessions.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Classes/Resources/database/variables.sql" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/SessionManager.php" dialect="MariaDB" />
|
||||
<file url="file://$PROJECT_DIR$/src/Socialbox/Managers/VariableManager.php" dialect="MariaDB" />
|
||||
<file url="file:///var/ncc/packages/net.nosial.socialbox=1.0.0/bin/src/Socialbox/Managers/VariableManager.php" dialect="MariaDB" />
|
||||
</component>
|
||||
</project>
|
|
@ -11,6 +11,8 @@
|
|||
],
|
||||
"require": {
|
||||
"ext-pdo": "*",
|
||||
"ext-openssl": "*"
|
||||
"ext-openssl": "*",
|
||||
"ext-redis": "*",
|
||||
"ext-memcached": "*"
|
||||
}
|
||||
}
|
|
@ -2,8 +2,15 @@
|
|||
|
||||
namespace Socialbox\Abstracts;
|
||||
|
||||
use RuntimeException;
|
||||
use Socialbox\Classes\CacheLayer\MemcachedCacheLayer;
|
||||
use Socialbox\Classes\CacheLayer\RedisCacheLayer;
|
||||
use Socialbox\Classes\Configuration;
|
||||
|
||||
abstract class CacheLayer
|
||||
{
|
||||
private static ?CacheLayer $instance = null;
|
||||
|
||||
/**
|
||||
* Stores a value in the cache with an associated key and an optional time-to-live (TTL).
|
||||
*
|
||||
|
@ -38,10 +45,46 @@ abstract class CacheLayer
|
|||
*/
|
||||
public abstract function exists(string $key): bool;
|
||||
|
||||
/**
|
||||
* Counts the number of items that start with the given prefix.
|
||||
*
|
||||
* @param string $prefix The prefix to search for.
|
||||
* @return int The count of items starting with the provided prefix.
|
||||
*/
|
||||
public abstract function getPrefixCount(string $prefix): int;
|
||||
|
||||
/**
|
||||
* Clears all values from the cache.
|
||||
*
|
||||
* @return bool Returns true if the cache was successfully cleared, false otherwise.
|
||||
*/
|
||||
public abstract function clear(): bool;
|
||||
|
||||
/**
|
||||
* Retrieves the singleton instance of the cache layer.
|
||||
*
|
||||
* @return CacheLayer The singleton instance of the cache layer.
|
||||
*/
|
||||
public static function getInstance(): CacheLayer
|
||||
{
|
||||
if (self::$instance === null)
|
||||
{
|
||||
$engine = Configuration::getConfiguration()['cache']['engine'];
|
||||
|
||||
if ($engine === 'redis')
|
||||
{
|
||||
self::$instance = new RedisCacheLayer();
|
||||
}
|
||||
else if ($engine === 'memcached')
|
||||
{
|
||||
self::$instance = new MemcachedCacheLayer();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException('Invalid cache engine specified in the configuration, must be either "redis" or "memcached".');
|
||||
}
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace Socialbox\Classes\CacheLayer;
|
|||
use Memcached;
|
||||
use RuntimeException;
|
||||
use Socialbox\Abstracts\CacheLayer;
|
||||
use Socialbox\Classes\Configuration;
|
||||
|
||||
class MemcachedCacheLayer extends CacheLayer
|
||||
{
|
||||
|
@ -12,11 +13,8 @@ class MemcachedCacheLayer extends CacheLayer
|
|||
|
||||
/**
|
||||
* Memcached cache layer constructor.
|
||||
*
|
||||
* @param string $host The Memcached server host.
|
||||
* @param int $port The Memcached server port.
|
||||
*/
|
||||
public function __construct(string $host, int $port)
|
||||
public function __construct()
|
||||
{
|
||||
if (!extension_loaded('memcached'))
|
||||
{
|
||||
|
@ -24,9 +22,10 @@ class MemcachedCacheLayer extends CacheLayer
|
|||
}
|
||||
|
||||
$this->memcached = new Memcached();
|
||||
if (!$this->memcached->addServer($host, $port))
|
||||
$this->memcached->addServer(Configuration::getConfiguration()['cache']['host'], (int)Configuration::getConfiguration()['cache']['port']);
|
||||
if(Configuration::getConfiguration()['cache']['username'] !== null || Configuration::getConfiguration()['cache']['password'] !== null)
|
||||
{
|
||||
throw new RuntimeException('Failed to connect to the Memcached server.');
|
||||
$this->memcached->setSaslAuthData(Configuration::getConfiguration()['cache']['username'], Configuration::getConfiguration()['cache']['password']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +79,25 @@ class MemcachedCacheLayer extends CacheLayer
|
|||
return $this->memcached->getResultCode() === Memcached::RES_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPrefixCount(string $prefix): int
|
||||
{
|
||||
$stats = $this->memcached->getStats();
|
||||
$count = 0;
|
||||
|
||||
foreach ($stats as $server => $data)
|
||||
{
|
||||
if (str_starts_with($server, $prefix))
|
||||
{
|
||||
$count += $data['curr_items'];
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
|
|
@ -6,6 +6,7 @@ use Redis;
|
|||
use RedisException;
|
||||
use RuntimeException;
|
||||
use Socialbox\Abstracts\CacheLayer;
|
||||
use Socialbox\Classes\Configuration;
|
||||
|
||||
class RedisCacheLayer extends CacheLayer
|
||||
{
|
||||
|
@ -13,12 +14,8 @@ class RedisCacheLayer extends CacheLayer
|
|||
|
||||
/**
|
||||
* Redis cache layer constructor.
|
||||
*
|
||||
* @param string $host The Redis server host.
|
||||
* @param int $port The Redis server port.
|
||||
* @param string|null $password Optional. The Redis server password.
|
||||
*/
|
||||
public function __construct(string $host, int $port, ?string $password=null)
|
||||
public function __construct()
|
||||
{
|
||||
if (!extension_loaded('redis'))
|
||||
{
|
||||
|
@ -29,10 +26,15 @@ class RedisCacheLayer extends CacheLayer
|
|||
|
||||
try
|
||||
{
|
||||
$this->redis->connect($host, $port);
|
||||
if ($password !== null)
|
||||
$this->redis->connect(Configuration::getConfiguration()['cache']['host'], (int)Configuration::getConfiguration()['cache']['port']);
|
||||
if (Configuration::getConfiguration()['cache']['password'] !== null)
|
||||
{
|
||||
$this->redis->auth($password);
|
||||
$this->redis->auth(Configuration::getConfiguration()['cache']['password']);
|
||||
}
|
||||
|
||||
if (Configuration::getConfiguration()['cache']['database'] !== 0)
|
||||
{
|
||||
$this->redis->select((int)Configuration::getConfiguration()['cache']['database']);
|
||||
}
|
||||
}
|
||||
catch (RedisException $e)
|
||||
|
@ -101,6 +103,21 @@ class RedisCacheLayer extends CacheLayer
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPrefixCount(string $prefix): int
|
||||
{
|
||||
try
|
||||
{
|
||||
return count($this->redis->keys($prefix . '*'));
|
||||
}
|
||||
catch (RedisException $e)
|
||||
{
|
||||
throw new RuntimeException('Failed to get the count of keys with the specified prefix in the Redis cache.', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
|
|
@ -2,13 +2,18 @@
|
|||
|
||||
namespace Socialbox\Classes\CliCommands;
|
||||
|
||||
use Exception;
|
||||
use LogLib\Log;
|
||||
use PDOException;
|
||||
use Socialbox\Abstracts\CacheLayer;
|
||||
use Socialbox\Classes\Configuration;
|
||||
use Socialbox\Classes\Cryptography;
|
||||
use Socialbox\Classes\Database;
|
||||
use Socialbox\Classes\Resources;
|
||||
use Socialbox\Enums\DatabaseObjects;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Interfaces\CliCommandInterface;
|
||||
use Socialbox\Managers\VariableManager;
|
||||
|
||||
class InitializeCommand implements CliCommandInterface
|
||||
{
|
||||
|
@ -23,7 +28,14 @@ class InitializeCommand implements CliCommandInterface
|
|||
return 1;
|
||||
}
|
||||
|
||||
print("Initializing Socialbox...\n");
|
||||
Log::info('net.nosial.socialbox', 'Initializing Socialbox...');
|
||||
|
||||
if(Configuration::getConfiguration()['cache']['enabled'])
|
||||
{
|
||||
Log::verbose('net.nosial.socialbox', 'Clearing cache layer...');
|
||||
CacheLayer::getInstance()->clear();
|
||||
}
|
||||
|
||||
foreach(DatabaseObjects::casesOrdered() as $object)
|
||||
{
|
||||
Log::verbose('net.nosial.socialbox', "Initializing database object {$object->value}");
|
||||
|
@ -46,13 +58,34 @@ class InitializeCommand implements CliCommandInterface
|
|||
return 1;
|
||||
}
|
||||
}
|
||||
catch(\Exception $e)
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::error('net.nosial.socialbox', "Failed to initialize database object {$object->value}: {$e->getMessage()}", $e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if(!VariableManager::variableExists('PUBLIC_KEY') || !VariableManager::variableExists('PRIVATE_KEY'))
|
||||
{
|
||||
Log::info('net.nosial.socialbox', 'Generating new key pair...');
|
||||
|
||||
$keyPair = Cryptography::generateKeyPair();
|
||||
VariableManager::setVariable('PUBLIC_KEY', $keyPair->getPublicKey());
|
||||
VariableManager::setVariable('PRIVATE_KEY', $keyPair->getPrivateKey());
|
||||
|
||||
Log::info('net.nosial.socialbox', 'Set the DNS TXT record for the public key to the following value:');
|
||||
Log::info('net.nosial.socialbox', "socialbox-key={$keyPair->getPublicKey()}");
|
||||
}
|
||||
}
|
||||
catch(DatabaseOperationException $e)
|
||||
{
|
||||
Log::error('net.nosial.socialbox', "Failed to generate key pair: {$e->getMessage()}", $e);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Log::info('net.nosial.socialbox', 'Socialbox has been initialized successfully');
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,18 @@ class Configuration
|
|||
$config->setDefault('database.username', 'root');
|
||||
$config->setDefault('database.password', 'root');
|
||||
$config->setDefault('database.name', 'test');
|
||||
|
||||
$config->setDefault('cache.enabled', false);
|
||||
$config->setDefault('cache.engine', 'redis');
|
||||
$config->setDefault('cache.host', '127.0.0.1');
|
||||
$config->setDefault('cache.port', 6379);
|
||||
$config->setDefault('cache.username', null);
|
||||
$config->setDefault('cache.password', null);
|
||||
$config->setDefault('cache.database', 0);
|
||||
$config->setDefault('cache.variables.enabled', true);
|
||||
$config->setDefault('cache.variables.ttl', 3600);
|
||||
$config->setDefault('cache.variables.max', 1000);
|
||||
|
||||
$config->save();
|
||||
|
||||
self::$configuration = $config->getConfiguration();
|
||||
|
|
11
src/Socialbox/Classes/Resources/database/variables.sql
Normal file
11
src/Socialbox/Classes/Resources/database/variables.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
create table variables
|
||||
(
|
||||
name varchar(255) not null comment 'The name of the variable'
|
||||
primary key comment 'The unique index for the variable name',
|
||||
value text null comment 'The value of the variable',
|
||||
`read_only` tinyint(1) default 0 not null comment 'Boolean indicator if the variable is read only',
|
||||
created timestamp default current_timestamp() not null comment 'The Timestamp for when this record was created',
|
||||
updated timestamp null comment 'The Timestamp for when this record was last updated',
|
||||
constraint variables_name_uindex
|
||||
unique (name) comment 'The unique index for the variable name'
|
||||
);
|
88
src/Socialbox/Classes/RpcClient.php
Normal file
88
src/Socialbox/Classes/RpcClient.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes;
|
||||
|
||||
use Socialbox\Classes\ServerResolver;
|
||||
use Socialbox\Enums\StandardHeaders;
|
||||
use Socialbox\Exceptions\ResolutionException;
|
||||
use Socialclient\Exceptions\RpcRequestException;
|
||||
|
||||
class RpcClient
|
||||
{
|
||||
private const string CLIENT_NAME = 'Socialbox PHP';
|
||||
private const string CLIENT_VERSION = '1.0';
|
||||
private const string CONTENT_TYPE = 'application/json';
|
||||
|
||||
private string $domain;
|
||||
private string $endpoint;
|
||||
private string $serverPublicKey;
|
||||
|
||||
|
||||
/**
|
||||
* @throws ResolutionException
|
||||
*/
|
||||
public function __construct(string $domain)
|
||||
{
|
||||
$resolved = ServerResolver::resolveDomain($domain);
|
||||
|
||||
$this->domain = $domain;
|
||||
$this->endpoint = $resolved->getEndpoint();
|
||||
$this->serverPublicKey = $resolved->getPublicKey();
|
||||
$this->clientPrivateKey = null;
|
||||
}
|
||||
|
||||
public function getDomain(): string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
public function getEndpoint(): string
|
||||
{
|
||||
return $this->endpoint;
|
||||
}
|
||||
|
||||
public function getServerPublicKey(): string
|
||||
{
|
||||
return $this->serverPublicKey;
|
||||
}
|
||||
|
||||
public function sendRequest(array $data)
|
||||
{
|
||||
$ch = curl_init($this->endpoint);
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, Utilities::jsonEncode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
Utilities::generateHeader(StandardHeaders::CLIENT_NAME, self::CLIENT_NAME),
|
||||
Utilities::generateHeader(StandardHeaders::CLIENT_VERSION, self::CLIENT_VERSION),
|
||||
Utilities::generateHeader(StandardHeaders::CONTENT_TYPE, self::CONTENT_TYPE)
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch))
|
||||
{
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
// Separate headers and body
|
||||
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$response_body = substr($response, $header_size);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Throw exception with response body as message and status code as code
|
||||
throw new RpcRequestException($response_body, $statusCode);
|
||||
}
|
||||
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
// Separate headers and body
|
||||
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$response_headers = substr($response, 0, $header_size);
|
||||
$response_body = substr($response, $header_size);
|
||||
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ use Socialbox\Enums\StandardHeaders;
|
|||
use Socialbox\Exceptions\CryptographyException;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Exceptions\RpcException;
|
||||
use Socialbox\Exceptions\StandardException;
|
||||
use Socialbox\Managers\SessionManager;
|
||||
use Socialbox\Objects\ClientRequest;
|
||||
use Socialbox\Objects\RpcRequest;
|
||||
|
@ -85,11 +86,6 @@ class RpcHandler
|
|||
|
||||
try
|
||||
{
|
||||
if(!SessionManager::sessionExists($clientRequest->getSessionUuid()))
|
||||
{
|
||||
throw new RpcException('Session UUID not found', 404);
|
||||
}
|
||||
|
||||
$session = SessionManager::getSession($clientRequest->getSessionUuid());
|
||||
|
||||
// Verify the signature of the request
|
||||
|
@ -98,6 +94,10 @@ class RpcHandler
|
|||
throw new RpcException('Request signature check failed', 400);
|
||||
}
|
||||
}
|
||||
catch(StandardException $e)
|
||||
{
|
||||
throw new RpcException($e->getMessage(), 400);
|
||||
}
|
||||
catch(CryptographyException $e)
|
||||
{
|
||||
throw new RpcException('Request signature check failed (Cryptography Error)', 400, $e);
|
||||
|
|
52
src/Socialbox/Classes/ServerResolver.php
Normal file
52
src/Socialbox/Classes/ServerResolver.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes;
|
||||
|
||||
use Socialbox\Exceptions\ResolutionException;
|
||||
use Socialbox\Objects\ResolvedServer;
|
||||
|
||||
class ServerResolver
|
||||
{
|
||||
/**
|
||||
* Resolves a given domain to fetch the RPC endpoint and public key from its DNS TXT records.
|
||||
*
|
||||
* @param string $domain The domain to be resolved.
|
||||
* @return ResolvedServer An instance of ResolvedServer containing the endpoint and public key.
|
||||
* @throws ResolutionException If the DNS TXT records cannot be resolved or if required information is missing.
|
||||
*/
|
||||
public static function resolveDomain(string $domain): ResolvedServer
|
||||
{
|
||||
$txtRecords = dns_get_record($domain, DNS_TXT);
|
||||
|
||||
if ($txtRecords === false)
|
||||
{
|
||||
throw new ResolutionException(sprintf("Failed to resolve DNS TXT records for %s", $domain));
|
||||
}
|
||||
|
||||
$endpoint = null;
|
||||
$publicKey = null;
|
||||
foreach ($txtRecords as $txt)
|
||||
{
|
||||
if (isset($txt['txt']) && str_starts_with($txt['txt'], 'socialbox='))
|
||||
{
|
||||
$endpoint = substr($txt['txt'], strlen('socialbox='));
|
||||
}
|
||||
elseif (isset($txt['txt']) && str_starts_with($txt['txt'], 'socialbox-key='))
|
||||
{
|
||||
$publicKey = substr($txt['txt'], strlen('socialbox-key='));
|
||||
}
|
||||
}
|
||||
|
||||
if ($endpoint === null)
|
||||
{
|
||||
throw new ResolutionException(sprintf("Failed to resolve RPC endpoint for %s", $domain));
|
||||
}
|
||||
|
||||
if ($publicKey === null)
|
||||
{
|
||||
throw new ResolutionException(sprintf("Failed to resolve public key for %s", $domain));
|
||||
}
|
||||
|
||||
return new ResolvedServer($endpoint, $publicKey);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ use InvalidArgumentException;
|
|||
use Socialbox\Abstracts\Method;
|
||||
use Socialbox\Enums\StandardError;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Exceptions\StandardException;
|
||||
use Socialbox\Interfaces\SerializableInterface;
|
||||
use Socialbox\Managers\SessionManager;
|
||||
use Socialbox\Objects\ClientRequest;
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Socialbox\Classes;
|
|||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Socialbox\Enums\StandardHeaders;
|
||||
|
||||
class Utilities
|
||||
{
|
||||
|
@ -34,6 +35,18 @@ class Utilities
|
|||
return $decoded;
|
||||
}
|
||||
|
||||
public static function jsonEncode(mixed $data): string
|
||||
{
|
||||
try
|
||||
{
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
|
||||
}
|
||||
catch(\JsonException $e)
|
||||
{
|
||||
throw new \RuntimeException("Failed to encode json input", $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given data in Base64.
|
||||
*
|
||||
|
@ -117,4 +130,9 @@ class Utilities
|
|||
$e->getTraceAsString()
|
||||
);
|
||||
}
|
||||
|
||||
public static function generateHeader(StandardHeaders $header, string $value): string
|
||||
{
|
||||
return $header->value . ': ' . $value;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ enum DatabaseObjects : string
|
|||
case PASSWORD_AUTHENTICATION = 'password_authentication.sql';
|
||||
case REGISTERED_PEERS = 'registered_peers.sql';
|
||||
case SESSIONS = 'sessions.sql';
|
||||
case VARIABLES = 'variables.sql';
|
||||
|
||||
/**
|
||||
* Returns the priority of the database object
|
||||
|
@ -17,6 +18,7 @@ enum DatabaseObjects : string
|
|||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::VARIABLES => 0,
|
||||
self::REGISTERED_PEERS => 1,
|
||||
self::PASSWORD_AUTHENTICATION, self::SESSIONS => 2,
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ enum StandardHeaders : string
|
|||
case SESSION_UUID = 'Session-UUID';
|
||||
case FROM_PEER = 'From-Peer';
|
||||
case SIGNATURE = 'Signature';
|
||||
case PUBLIC_KEY = 'Public-Key';
|
||||
|
||||
/**
|
||||
* Determines if the current instance is required based on its type.
|
||||
|
|
13
src/Socialbox/Exceptions/ResolutionException.php
Normal file
13
src/Socialbox/Exceptions/ResolutionException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class ResolutionException extends \Exception
|
||||
{
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
use Socialbox\Enums\SessionState;
|
||||
use Socialbox\Enums\StandardError;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Exceptions\StandardException;
|
||||
use Socialbox\Objects\SessionRecord;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
|
@ -88,6 +89,7 @@
|
|||
* @param string $uuid The unique identifier of the session.
|
||||
* @return SessionRecord The session record corresponding to the given UUID.
|
||||
* @throws DatabaseOperationException If the session record cannot be found or if there is an error during retrieval.
|
||||
* @throws StandardException
|
||||
*/
|
||||
public static function getSession(string $uuid): SessionRecord
|
||||
{
|
||||
|
@ -100,7 +102,7 @@
|
|||
|
||||
if ($data === false)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Session record %s not found', $uuid));
|
||||
throw new StandardException(sprintf("The requested session '%s' does not exist"), StandardError::SESSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Convert the timestamp fields to DateTime objects
|
||||
|
|
151
src/Socialbox/Managers/VariableManager.php
Normal file
151
src/Socialbox/Managers/VariableManager.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Managers;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use Socialbox\Abstracts\CacheLayer;
|
||||
use Socialbox\Classes\Configuration;
|
||||
use Socialbox\Classes\Database;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
|
||||
class VariableManager
|
||||
{
|
||||
/**
|
||||
* Sets a variable in the database. If the variable already exists, its value is updated.
|
||||
*
|
||||
* @param string $name The name of the variable.
|
||||
* @param string $value The value of the variable.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If the operation fails.
|
||||
*/
|
||||
public static function setVariable(string $name, string $value): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("INSERT INTO variables (name, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value=?");
|
||||
$statement->bindParam(1, $name);
|
||||
$statement->bindParam(2, $value);
|
||||
$statement->bindParam(3, $value);
|
||||
$statement->execute();
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Failed to set variable %s in the database', $name), $e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(Configuration::getConfiguration()['cache']['enabled'] && Configuration::getConfiguration()['cache']['variables']['enabled'])
|
||||
{
|
||||
if(Configuration::getConfiguration()['cache']['variables']['max'] > 0)
|
||||
{
|
||||
if(CacheLayer::getInstance()->getPrefixCount('VARIABLES_') >= Configuration::getConfiguration()['cache']['variables']['max'])
|
||||
{
|
||||
// Return early if the cache is full
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CacheLayer::getInstance()->set(sprintf("VARIABLES_%s", $name), $value, (int)Configuration::getConfiguration()['cache']['variables']['ttl']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a variable from the database based on its name.
|
||||
*
|
||||
* @param string $name The name of the variable to retrieve.
|
||||
* @return string The value of the variable.
|
||||
* @throws DatabaseOperationException If the database operation fails.
|
||||
*/
|
||||
public static function getVariable(string $name): string
|
||||
{
|
||||
if(Configuration::getConfiguration()['cache']['enabled'] && Configuration::getConfiguration()['cache']['variables']['enabled'])
|
||||
{
|
||||
$cachedValue = CacheLayer::getInstance()->get(sprintf("VARIABLES_%s", $name));
|
||||
if($cachedValue !== false)
|
||||
{
|
||||
return $cachedValue;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT value FROM variables WHERE name=?");
|
||||
$statement->bindParam(1, $name);
|
||||
$statement->execute();
|
||||
|
||||
if($statement->rowCount() === 0)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Variable with name %s does not exist', $name));
|
||||
}
|
||||
|
||||
$result = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
return $result['value'];
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Failed to get variable %s from the database', $name), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a variable with the specified name exists in the database.
|
||||
*
|
||||
* @param string $name The name of the variable to check for existence.
|
||||
* @return bool Returns true if the variable exists, false otherwise.
|
||||
* @throws DatabaseOperationException If the database operation fails.
|
||||
*/
|
||||
public static function variableExists(string $name): bool
|
||||
{
|
||||
if(Configuration::getConfiguration()['cache']['enabled'] && Configuration::getConfiguration()['cache']['variables']['enabled'])
|
||||
{
|
||||
$cachedValue = CacheLayer::getInstance()->get(sprintf("VARIABLES_%s", $name));
|
||||
if($cachedValue !== false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("SELECT COUNT(*) FROM variables WHERE name=?");
|
||||
$statement->bindParam(1, $name);
|
||||
$statement->execute();
|
||||
$result = $statement->fetchColumn();
|
||||
return $result > 0;
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Failed to check if the variable %s exists', $name), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a variable from the database using the provided name.
|
||||
*
|
||||
* @param string $name The name of the variable to be deleted.
|
||||
* @return void
|
||||
* @throws DatabaseOperationException If the database operation fails.
|
||||
*/
|
||||
public static function deleteVariable(string $name): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$statement = Database::getConnection()->prepare("DELETE FROM variables WHERE name=?");
|
||||
$statement->bindParam(1, $name);
|
||||
$statement->execute();
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
throw new DatabaseOperationException(sprintf('Failed to delete variable %s from the database', $name), $e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(Configuration::getConfiguration()['cache']['enabled'] && Configuration::getConfiguration()['cache']['variables']['enabled'])
|
||||
{
|
||||
CacheLayer::getInstance()->delete(sprintf("VARIABLES_%s", $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,14 @@
|
|||
|
||||
namespace Socialbox\Objects;
|
||||
|
||||
use RuntimeException;
|
||||
use Socialbox\Classes\Cryptography;
|
||||
use Socialbox\Enums\StandardError;
|
||||
use Socialbox\Enums\StandardHeaders;
|
||||
use Socialbox\Exceptions\CryptographyException;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Exceptions\StandardException;
|
||||
use Socialbox\Managers\SessionManager;
|
||||
|
||||
class ClientRequest
|
||||
{
|
||||
|
@ -95,15 +102,41 @@ class ClientRequest
|
|||
return $this->headers[StandardHeaders::SIGNATURE->value];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws DatabaseOperationException
|
||||
*/
|
||||
public function verifySignature(): bool
|
||||
{
|
||||
$signature = $this->getSignature();
|
||||
$sessionUuid = $this->getSessionUuid();
|
||||
|
||||
if($signature == null)
|
||||
if($signature == null || $sessionUuid == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$session = SessionManager::getSession($sessionUuid);
|
||||
}
|
||||
catch(StandardException $e)
|
||||
{
|
||||
if($e->getStandardError() == StandardError::SESSION_NOT_FOUND)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new RuntimeException($e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Cryptography::verifyContent($this->getHash(), $signature, $session->getPublicKey());
|
||||
}
|
||||
catch(CryptographyException $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
25
src/Socialbox/Objects/ResolvedServer.php
Normal file
25
src/Socialbox/Objects/ResolvedServer.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Objects;
|
||||
|
||||
class ResolvedServer
|
||||
{
|
||||
private string $endpoint;
|
||||
private string $publicKey;
|
||||
|
||||
public function __construct(string $endpoint, string $publicKey)
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
$this->publicKey = $publicKey;
|
||||
}
|
||||
|
||||
public function getEndpoint(): string
|
||||
{
|
||||
return $this->endpoint;
|
||||
}
|
||||
|
||||
public function getPublicKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
}
|
|
@ -55,13 +55,6 @@ class RpcResponse implements SerializableInterface
|
|||
return $data->toArray();
|
||||
}
|
||||
|
||||
// If the data is an array, recursively apply this method to each element
|
||||
if (is_array($data))
|
||||
{
|
||||
return array_map([$this, 'convertToArray'], $data);
|
||||
}
|
||||
|
||||
// Otherwise, return the data as-is (e.g., for scalar values)
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ class CryptographyTest extends TestCase
|
|||
$this->assertObjectHasProperty('privateKey', $keyPair);
|
||||
$this->assertIsString($keyPair->getPublicKey());
|
||||
$this->assertIsString($keyPair->getPrivateKey());
|
||||
|
||||
print_r($keyPair);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
21
tests/Socialbox/Classes/ServerResolverTest.php
Normal file
21
tests/Socialbox/Classes/ServerResolverTest.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Classes;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Socialbox\Exceptions\ResolutionException;
|
||||
use Socialbox\Objects\ResolvedServer;
|
||||
|
||||
class ServerResolverTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test for the function resolveDomain of the class ServerResolver
|
||||
*/
|
||||
public function testResolveDomain(): void
|
||||
{
|
||||
// successful resolution
|
||||
$resolvedServer = ServerResolver::resolveDomain('n64.cc');
|
||||
self::assertNotEmpty($resolvedServer->getEndpoint());
|
||||
self::assertNotEmpty($resolvedServer->getPublicKey());
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ use InvalidArgumentException;
|
|||
use PHPUnit\Framework\TestCase;
|
||||
use Socialbox\Classes\Cryptography;
|
||||
use Socialbox\Classes\Utilities;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Objects\SessionRecord;
|
||||
|
||||
class SessionManagerTest extends TestCase
|
||||
|
@ -27,14 +26,6 @@ class SessionManagerTest extends TestCase
|
|||
$this->assertTrue(SessionManager::sessionExists($uuid));
|
||||
}
|
||||
|
||||
public function testGetSessionWithInvalidUuid(): void
|
||||
{
|
||||
$uuid = 'invalid_uuid';
|
||||
|
||||
$this->expectException(DatabaseOperationException::class);
|
||||
SessionManager::getSession($uuid);
|
||||
}
|
||||
|
||||
public function testGetSessionWithValidUuid(): void
|
||||
{
|
||||
$keyPair = Cryptography::generateKeyPair();
|
||||
|
|
34
tests/Socialbox/Managers/VariableManagerTest.php
Normal file
34
tests/Socialbox/Managers/VariableManagerTest.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Socialbox\Managers;
|
||||
|
||||
use PDOException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Socialbox\Abstracts\CacheLayer;
|
||||
use Socialbox\Exceptions\DatabaseOperationException;
|
||||
use Socialbox\Managers\VariableManager;
|
||||
|
||||
class VariableManagerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test the setter method for a variable in the VariableManager class.
|
||||
*
|
||||
*/
|
||||
public function testSetVariable(): void
|
||||
{
|
||||
CacheLayer::getInstance()->clear();
|
||||
|
||||
VariableManager::deleteVariable('test_name');
|
||||
VariableManager::setVariable('test_name', 'test_value');
|
||||
$this->assertTrue(VariableManager::variableExists('test_name'));
|
||||
$this->assertEquals('test_value', VariableManager::getVariable('test_name'));
|
||||
VariableManager::deleteVariable('test_name');
|
||||
|
||||
VariableManager::deleteVariable('test_name2');
|
||||
VariableManager::setVariable('test_name2', 'test_value2');
|
||||
$this->assertTrue(VariableManager::variableExists('test_name2'));
|
||||
$this->assertEquals('test_value2', VariableManager::getVariable('test_name2'));
|
||||
VariableManager::deleteVariable('test_name2');
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue