Initial Commit
This commit is contained in:
parent
93a0b9be02
commit
6e599b2c0c
99 changed files with 10836 additions and 4 deletions
149
src/FederationCLI/InteractiveMode.php
Normal file
149
src/FederationCLI/InteractiveMode.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationCLI;
|
||||
|
||||
use FederationLib\FederationLib;
|
||||
|
||||
class InteractiveMode
|
||||
{
|
||||
/**
|
||||
* The current menu the user is in
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $current_menu = 'Main';
|
||||
|
||||
/**
|
||||
* An array of menu pointers to functions
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $menu_pointers = [
|
||||
'ClientManager' => 'FederationCLI\InteractiveMode\ClientManager::processCommand',
|
||||
'ConfigManager' => 'FederationCLI\InteractiveMode\ConfigurationManager::processCommand'
|
||||
];
|
||||
|
||||
private static $help_pointers =[
|
||||
'ClientManager' => 'FederationCLI\InteractiveMode\ClientManager::help',
|
||||
'ConfigManager' => 'FederationCLI\InteractiveMode\ConfigurationManager::help'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var FederationLib|null
|
||||
*/
|
||||
private static $federation_lib = null;
|
||||
|
||||
/**
|
||||
* Main entry point for the interactive mode
|
||||
*
|
||||
* @param array $args
|
||||
* @return void
|
||||
*/
|
||||
public static function main(array $args=[]): void
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
print(sprintf('federation@%s:~$ ', self::$current_menu));
|
||||
$input = trim(fgets(STDIN));
|
||||
|
||||
self::processCommand($input);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a command from the user
|
||||
*
|
||||
* @param string $input
|
||||
* @return void
|
||||
*/
|
||||
private static function processCommand(string $input): void
|
||||
{
|
||||
$parsed_input = Utilities::parseShellInput($input);
|
||||
|
||||
switch(strtolower($parsed_input['command']))
|
||||
{
|
||||
case 'help':
|
||||
print('Available commands:' . PHP_EOL);
|
||||
print(' help - displays the help menu for the current menu and global commands' . PHP_EOL);
|
||||
print(' client_manager - enter client manager mode' . PHP_EOL);
|
||||
print(' config_manager - enter config manager mode' . PHP_EOL);
|
||||
print(' clear - clears the screen' . PHP_EOL);
|
||||
print(' exit - exits the current menu, if on the main menu, exits the program' . PHP_EOL);
|
||||
|
||||
if(isset(self::$help_pointers[self::$current_menu]))
|
||||
{
|
||||
call_user_func(self::$help_pointers[self::$current_menu]);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'client_manager':
|
||||
self::$current_menu = 'ClientManager';
|
||||
break;
|
||||
|
||||
case 'config_manager':
|
||||
self::$current_menu = 'ConfigManager';
|
||||
break;
|
||||
|
||||
case 'clear':
|
||||
print(chr(27) . "[2J" . chr(27) . "[;H");
|
||||
break;
|
||||
|
||||
case 'exit':
|
||||
if(self::$current_menu != 'Main')
|
||||
{
|
||||
self::$current_menu = 'Main';
|
||||
break;
|
||||
}
|
||||
|
||||
exit(0);
|
||||
|
||||
default:
|
||||
if(!isset(self::$menu_pointers[self::$current_menu]))
|
||||
{
|
||||
print(sprintf('Unknown command: %s', $parsed_input['command']) . PHP_EOL);
|
||||
break;
|
||||
}
|
||||
|
||||
call_user_func(self::$menu_pointers[self::$current_menu], $input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current menu
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentMenu(): string
|
||||
{
|
||||
return self::$current_menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current menu to the specified value
|
||||
*
|
||||
* @param string $current_menu
|
||||
*/
|
||||
public static function setCurrentMenu(string $current_menu): void
|
||||
{
|
||||
self::$current_menu = $current_menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FederationLib instance
|
||||
*
|
||||
* @return FederationLib
|
||||
*/
|
||||
public static function getFederationLib(): FederationLib
|
||||
{
|
||||
if(self::$federation_lib == null)
|
||||
{
|
||||
self::$federation_lib = new FederationLib();
|
||||
}
|
||||
|
||||
return self::$federation_lib;
|
||||
}
|
||||
}
|
190
src/FederationCLI/InteractiveMode/ClientManager.php
Normal file
190
src/FederationCLI/InteractiveMode/ClientManager.php
Normal file
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
namespace FederationCLI\InteractiveMode;
|
||||
|
||||
use AsciiTable\Builder;
|
||||
use Exception;
|
||||
use FederationCLI\InteractiveMode;
|
||||
use FederationCLI\Utilities;
|
||||
use FederationLib\Enums\FilterOrder;
|
||||
use FederationLib\Enums\Filters\ListClientsFilter;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Objects\Client;
|
||||
|
||||
class ClientManager
|
||||
{
|
||||
/**
|
||||
* @param string $input
|
||||
* @return void
|
||||
*/
|
||||
public static function processCommand(string $input): void
|
||||
{
|
||||
$parsed_input = Utilities::parseShellInput($input);
|
||||
|
||||
try
|
||||
{
|
||||
switch(strtolower($parsed_input['command']))
|
||||
{
|
||||
case 'register':
|
||||
self::registerClient();
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
// list [optional: page] [optional: filter] [optional: order] [optional: max_items]
|
||||
self::listClients($parsed_input['args']);
|
||||
break;
|
||||
|
||||
case 'total':
|
||||
self::totalClients();
|
||||
break;
|
||||
|
||||
case 'total_pages':
|
||||
self::totalPages($parsed_input['args']);
|
||||
break;
|
||||
|
||||
case 'get':
|
||||
self::getClient($parsed_input['args']);
|
||||
break;
|
||||
|
||||
default:
|
||||
print(sprintf('Unknown command: %s', $parsed_input['command']) . PHP_EOL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Utilities::printExceptionStack($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the help message for the client manager
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function help(): void
|
||||
{
|
||||
print('Client manager commands:' . PHP_EOL);
|
||||
print(' register - registers a new client with the federation' . PHP_EOL);
|
||||
print(' list [optional: page (default 1)] [optional: filter (default created_timestamp)] [optional: order (default asc)] [optional: max_items (default 100)] - lists clients' . PHP_EOL);
|
||||
print(' total - gets the total number of clients' . PHP_EOL);
|
||||
print(' total_pages [optional: max_items (default 100)] - gets the total number of pages of clients' . PHP_EOL);
|
||||
print(' get [client uuid] - gets a client by UUID' . PHP_EOL);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new client with the federation. prints the UUID of the client if successful.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function registerClient(): void
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
$client->setName(Utilities::promptInput('Client name (default: Random): '));
|
||||
$client->setDescription(Utilities::promptInput('Client description (default: N/A): '));
|
||||
$client->setQueryPermission((int)Utilities::parseBoolean(Utilities::promptInput('Query permission (default: 1): ')));
|
||||
$client->setUpdatePermission((int)Utilities::parseBoolean(Utilities::promptInput('Update permission (default: 0): ')));
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
$client_uuid = InteractiveMode::getFederationLib()->getClientManager()->registerClient($client);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
print('Failed to register client: ' . $e->getMessage() . PHP_EOL);
|
||||
Utilities::printExceptionStack($e);
|
||||
return;
|
||||
}
|
||||
|
||||
print(sprintf('Client registered successfully, UUID: %s', $client_uuid) . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
* @throws \RedisException
|
||||
*/
|
||||
private static function listClients(array $args): void
|
||||
{
|
||||
$page = $args[0] ?? 1;
|
||||
$filter = $args[1] ?? ListClientsFilter::CREATED_TIMESTAMP;
|
||||
$order = $args[2] ?? FilterOrder::ASCENDING;
|
||||
$max_items = $args[3] ?? 100;
|
||||
|
||||
$clients = InteractiveMode::getFederationLib()->getClientManager()->listClients($page, $filter, $order, $max_items);
|
||||
|
||||
if(count($clients) === 0)
|
||||
{
|
||||
print('No clients found' . PHP_EOL);
|
||||
}
|
||||
else
|
||||
{
|
||||
$builder = new Builder();
|
||||
foreach($clients as $client)
|
||||
{
|
||||
$builder->addRow($client->toArray());
|
||||
}
|
||||
$builder->setTitle(sprintf('Clients (page %d, filter %s, order %s, max items %d)', $page, $filter, $order, $max_items));
|
||||
print($builder->renderTable() . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
private static function getClient(mixed $args)
|
||||
{
|
||||
$client_uuid = $args[0] ?? null;
|
||||
|
||||
if(is_null($client_uuid))
|
||||
{
|
||||
print('Client UUID required' . PHP_EOL);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$client = InteractiveMode::getFederationLib()->getClientManager()->getClient($client_uuid);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
print('Failed to get client: ' . $e->getMessage() . PHP_EOL);
|
||||
Utilities::printExceptionStack($e);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($client->toArray() as $key => $value)
|
||||
{
|
||||
print match ($key)
|
||||
{
|
||||
'id' => (sprintf(' UUID: %s', $value) . PHP_EOL),
|
||||
'enabled' => (sprintf(' Enabled: %s', (Utilities::parseBoolean($value) ? 'Yes' : 'No')) . PHP_EOL),
|
||||
'name' => (sprintf(' Name: %s', $value ?? 'N/A') . PHP_EOL),
|
||||
'description' => (sprintf(' Description: %s', $value ?? 'N/A') . PHP_EOL),
|
||||
'secret_totp' => (sprintf(' Secret TOTP: %s', $value ?? 'N/A') . PHP_EOL),
|
||||
'query_permission' => (sprintf(' Query permission Level: %s', $value) . PHP_EOL),
|
||||
'update_permission' => (sprintf(' Update permission Level: %s', $value) . PHP_EOL),
|
||||
'flags' => (sprintf(' Flags: %s', $value) . PHP_EOL),
|
||||
'created_timestamp' => (sprintf(' Created: %s', date('Y-m-d H:i:s', $value)) . PHP_EOL),
|
||||
'updated_timestamp' => (sprintf(' Updated: %s', date('Y-m-d H:i:s', $value)) . PHP_EOL),
|
||||
'seen_timestamp' => (sprintf(' Last seen: %s', date('Y-m-d H:i:s', $value)) . PHP_EOL),
|
||||
default => (sprintf(' %s: %s', $key, $value) . PHP_EOL),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static function totalClients()
|
||||
{
|
||||
print(sprintf('Total clients: %d', InteractiveMode::getFederationLib()->getClientManager()->getTotalClients()) . PHP_EOL);
|
||||
}
|
||||
|
||||
private static function totalPages(mixed $args)
|
||||
{
|
||||
$max_items = $args[0] ?? 100;
|
||||
|
||||
print(sprintf('Total pages: %d', InteractiveMode::getFederationLib()->getClientManager()->getTotalPages($max_items)) . PHP_EOL);
|
||||
}
|
||||
}
|
113
src/FederationCLI/InteractiveMode/ConfigurationManager.php
Normal file
113
src/FederationCLI/InteractiveMode/ConfigurationManager.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
namespace FederationCLI\InteractiveMode;
|
||||
|
||||
use Exception;
|
||||
use FederationCLI\Utilities;
|
||||
use FederationLib\Classes\Configuration;
|
||||
|
||||
class ConfigurationManager
|
||||
{
|
||||
/**
|
||||
* @param string $input
|
||||
* @return void
|
||||
*/
|
||||
public static function processCommand(string $input): void
|
||||
{
|
||||
$parsed_input = Utilities::parseShellInput($input);
|
||||
|
||||
switch(strtolower($parsed_input['command']))
|
||||
{
|
||||
case 'read':
|
||||
self::read($parsed_input['args'][0] ?? null);
|
||||
break;
|
||||
|
||||
case 'write':
|
||||
self::write($parsed_input['args'][0] ?? null, $parsed_input['args'][1] ?? null);
|
||||
break;
|
||||
|
||||
case 'save':
|
||||
self::save();
|
||||
break;
|
||||
|
||||
default:
|
||||
print(sprintf('Unknown command: %s', $parsed_input['command']) . PHP_EOL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the help message for the client manager
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function help(): void
|
||||
{
|
||||
print('Configuration manager commands:' . PHP_EOL);
|
||||
print(' read - reads the current configuration' . PHP_EOL);
|
||||
print(' read <key> - reads the value of the specified configuration key' . PHP_EOL);
|
||||
print(' write <key> <value> - writes the value of the specified configuration key' . PHP_EOL);
|
||||
print(' save - saves the current configuration to disk' . PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the current configuration or the value of a specific key
|
||||
*
|
||||
* @param string|null $key
|
||||
* @return void
|
||||
*/
|
||||
private static function read(?string $key=null): void
|
||||
{
|
||||
if($key === null)
|
||||
{
|
||||
$value = Configuration::getConfiguration();
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = Configuration::getConfigurationObject()->get($key);
|
||||
}
|
||||
|
||||
if(is_null($value))
|
||||
{
|
||||
print('No value found for key: ' . $key . PHP_EOL);
|
||||
}
|
||||
elseif(is_array($value))
|
||||
{
|
||||
print(json_encode($value, JSON_PRETTY_PRINT) . PHP_EOL);
|
||||
}
|
||||
else
|
||||
{
|
||||
print($value . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value of a specific configuration key
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @return void
|
||||
*/
|
||||
private static function write(string $key, string $value): void
|
||||
{
|
||||
Configuration::getConfigurationObject()->set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the current configuration to disk
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function save(): void
|
||||
{
|
||||
try
|
||||
{
|
||||
Configuration::getConfigurationObject()->save();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
print('Failed to save configuration: ' . $e->getMessage() . PHP_EOL);
|
||||
Utilities::printExceptionStack($e);
|
||||
}
|
||||
}
|
||||
}
|
41
src/FederationCLI/Program.php
Normal file
41
src/FederationCLI/Program.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace FederationCLI;
|
||||
|
||||
use ncc\Runtime;
|
||||
|
||||
class Program
|
||||
{
|
||||
/**
|
||||
* Main entry point for the CLI
|
||||
*
|
||||
* @param array $args
|
||||
* @return void
|
||||
*/
|
||||
public static function main(array $args=[]): void
|
||||
{
|
||||
if (isset($args['shell']))
|
||||
{
|
||||
InteractiveMode::main($args);
|
||||
}
|
||||
|
||||
self::help();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the help message
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function help(): void
|
||||
{
|
||||
print('FederationLib v' . Runtime::getConstant('net.nosial.federationlib', 'version') . PHP_EOL . PHP_EOL);
|
||||
|
||||
print('Usage: federationlib [command] [options]' . PHP_EOL);
|
||||
print('Commands:' . PHP_EOL);
|
||||
print(' help - show this help' . PHP_EOL);
|
||||
print(' shell - enter interactive mode' . PHP_EOL);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
}
|
101
src/FederationCLI/Utilities.php
Normal file
101
src/FederationCLI/Utilities.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace FederationCLI;
|
||||
|
||||
class Utilities
|
||||
{
|
||||
/**
|
||||
* Parses the shell input into a command and arguments array
|
||||
*
|
||||
* @param string $input
|
||||
* @return array
|
||||
*/
|
||||
public static function parseShellInput(string $input): array
|
||||
{
|
||||
$parsed = explode(' ', $input);
|
||||
$command = array_shift($parsed);
|
||||
$args = $parsed;
|
||||
|
||||
return [
|
||||
'command' => $command,
|
||||
'args' => $args
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a boolean value from a string
|
||||
*
|
||||
* @param $input
|
||||
* @return bool
|
||||
*/
|
||||
public static function parseBoolean($input): bool
|
||||
{
|
||||
if(is_null($input))
|
||||
return false;
|
||||
|
||||
if(is_bool($input))
|
||||
return $input;
|
||||
|
||||
if(is_numeric($input))
|
||||
return (bool) $input;
|
||||
|
||||
if(is_string($input))
|
||||
$input = trim($input);
|
||||
|
||||
switch(strtolower($input))
|
||||
{
|
||||
case 'true':
|
||||
case 'yes':
|
||||
case 'y':
|
||||
case '1':
|
||||
return true;
|
||||
|
||||
default:
|
||||
case 'false':
|
||||
case 'no':
|
||||
case 'n':
|
||||
case '0':
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for an input value
|
||||
*
|
||||
* @param string|null $prompt
|
||||
* @param string|null $default_value
|
||||
* @return string|null
|
||||
*/
|
||||
public static function promptInput(?string $prompt=null, ?string $default_value=null): ?string
|
||||
{
|
||||
if($prompt)
|
||||
print($prompt . ' ');
|
||||
|
||||
$input = trim(fgets(STDIN));
|
||||
|
||||
if(!$input && $default_value)
|
||||
$input = $default_value;
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for a boolean value
|
||||
*
|
||||
* @param string|null $prompt
|
||||
* @param bool $default_value
|
||||
* @return bool
|
||||
*/
|
||||
public static function promptYesNo(?string $prompt=null, bool $default_value=false): bool
|
||||
{
|
||||
if($prompt)
|
||||
print($prompt . ' ');
|
||||
|
||||
$input = trim(fgets(STDIN));
|
||||
|
||||
if(!$input && $default_value)
|
||||
$input = $default_value;
|
||||
|
||||
return self::parseBoolean($input);
|
||||
}
|
||||
}
|
84
src/FederationLib/Classes/Base32.php
Normal file
84
src/FederationLib/Classes/Base32.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
class Base32
|
||||
{
|
||||
/**
|
||||
* The base32 alphabet. (RFC 4648)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
|
||||
/**
|
||||
* Encodes a string to base32.
|
||||
*
|
||||
* @param $string
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($string): string
|
||||
{
|
||||
$binary = '';
|
||||
$output = '';
|
||||
foreach (str_split($string) as $char)
|
||||
{
|
||||
$binary .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
$padding = 5 - (strlen($binary) % 5);
|
||||
|
||||
if ($padding !== 5)
|
||||
{
|
||||
$binary .= str_repeat('0', $padding);
|
||||
}
|
||||
|
||||
$chunks = str_split($binary, 5);
|
||||
foreach ($chunks as $chunk)
|
||||
{
|
||||
$output .= self::$alphabet[bindec($chunk)];
|
||||
}
|
||||
|
||||
$padding = (strlen($output) % 8 === 0) ? 0 : (8 - (strlen($output) % 8));
|
||||
if ($padding !== 0)
|
||||
{
|
||||
$output .= str_repeat('=', $padding);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base32 encoded string.
|
||||
*
|
||||
* @param $string
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($string): string
|
||||
{
|
||||
$binary = '';
|
||||
$output = '';
|
||||
|
||||
foreach (str_split($string) as $char)
|
||||
{
|
||||
if ($char === '=')
|
||||
break;
|
||||
$binary .= str_pad(decbin(strpos(self::$alphabet, $char)), 5, '0', STR_PAD_LEFT);
|
||||
}
|
||||
$padding = 8 - (strlen($binary) % 8);
|
||||
|
||||
if ($padding !== 8)
|
||||
{
|
||||
$binary = substr($binary, 0, -$padding);
|
||||
}
|
||||
|
||||
$chunks = str_split($binary, 8);
|
||||
|
||||
foreach ($chunks as $chunk)
|
||||
{
|
||||
$output .= chr(bindec($chunk));
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
7
src/FederationLib/Classes/CacheSystem.php
Normal file
7
src/FederationLib/Classes/CacheSystem.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
class CacheSystem
|
||||
{
|
||||
}
|
226
src/FederationLib/Classes/Configuration.php
Normal file
226
src/FederationLib/Classes/Configuration.php
Normal file
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration\CacheServerConfiguration;
|
||||
use RuntimeException;
|
||||
|
||||
class Configuration
|
||||
{
|
||||
/**
|
||||
* @var \ConfigLib\Configuration|null
|
||||
*/
|
||||
private static $configuration;
|
||||
|
||||
/**
|
||||
* Returns the full raw configuration array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getConfiguration(): array
|
||||
{
|
||||
if(self::$configuration === null)
|
||||
{
|
||||
self::$configuration = new \ConfigLib\Configuration('federation');
|
||||
|
||||
/** Database Configuration */
|
||||
self::$configuration->setDefault('database.driver', 'mysqli');
|
||||
self::$configuration->setDefault('database.host', 'localhost');
|
||||
self::$configuration->setDefault('database.port', 3306);
|
||||
self::$configuration->setDefault('database.name', 'federation');
|
||||
self::$configuration->setDefault('database.username', 'root');
|
||||
self::$configuration->setDefault('database.password', 'root');
|
||||
self::$configuration->setDefault('database.charset', 'utf8mb4');
|
||||
self::$configuration->setDefault('database.reconnect_interval', 1800);
|
||||
|
||||
/** Multi-Cache Configuration */
|
||||
self::$configuration->setDefault('cache_system.enabled', true);
|
||||
// Cache System Configuration
|
||||
// Client Objects
|
||||
self::$configuration->setDefault('cache_system.cache.client_objects_enabled', true);
|
||||
self::$configuration->setDefault('cache_system.cache.client_objects_ttl', 200);
|
||||
self::$configuration->setDefault('cache_system.cache.client_objects_server_preference', 'redis_master');
|
||||
self::$configuration->setDefault('cache_system.cache.client_objects_server_fallback', 'any');
|
||||
// Peer Objects
|
||||
self::$configuration->setDefault('cache_system.cache.peer_objects_enabled', true);
|
||||
self::$configuration->setDefault('cache_system.cache.peer_objects_ttl', 200);
|
||||
self::$configuration->setDefault('cache_system.cache.peer_objects_server_preference', 'redis_master');
|
||||
self::$configuration->setDefault('cache_system.cache.peer_objects_server_fallback', 'any');
|
||||
// Redis Configuration
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.enabled', true);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.host', 'localhost');
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.port', 6379);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.driver', 'redis');
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.priority', 1);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.username', null);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.password', null);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.database', null);
|
||||
self::$configuration->setDefault('cache_system.servers.redis_master.reconnect_interval', 1800);
|
||||
// Memcached Configuration
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.enabled', false);
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.host', 'localhost');
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.port', 11211);
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.driver', 'memcached');
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.priority', 1);
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.username', null);
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.password', null);
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.database', null);
|
||||
self::$configuration->setDefault('cache_system.servers.memcached_master.reconnect_interval', 1800);
|
||||
|
||||
|
||||
|
||||
/** Federation Configuration */
|
||||
self::$configuration->setDefault('federation.events_retention', 1209600); // Two Weeks
|
||||
self::$configuration->setDefault('federation.events_enabled.generic', true);
|
||||
self::$configuration->setDefault('federation.events_enabled.client_Created', true);
|
||||
|
||||
|
||||
/** Save the configuration's default values if they don't exist */
|
||||
try
|
||||
{
|
||||
self::$configuration->save();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new RuntimeException('Failed to save configuration: ' . $e->getMessage(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$configuration->getConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration object.
|
||||
*
|
||||
* @return \ConfigLib\Configuration
|
||||
*/
|
||||
public static function getConfigurationObject(): \ConfigLib\Configuration
|
||||
{
|
||||
if(self::$configuration === null)
|
||||
{
|
||||
self::getConfiguration();
|
||||
}
|
||||
|
||||
return self::$configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns driver of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseDriver(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['driver'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseHost(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['host'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port of the database.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getDatabasePort(): int
|
||||
{
|
||||
return (int)self::getConfiguration()['database']['port'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseName(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseUsername(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['username'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the password of the database.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getDatabasePassword(): ?string
|
||||
{
|
||||
$password = self::getConfiguration()['database']['password'];
|
||||
|
||||
if($password === '')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the charset of the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDatabaseCharset(): string
|
||||
{
|
||||
return self::getConfiguration()['database']['charset'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interval in seconds to reconnect to the database.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getDatabaseReconnectInterval(): int
|
||||
{
|
||||
return (int)self::getConfiguration()['database']['reconnect_interval'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if the cache system is enabled for FederationLib
|
||||
* and False if not, based on the configuration.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isCacheSystemEnabled(): bool
|
||||
{
|
||||
return (bool)self::getConfiguration()['cache_system']['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for all the cache servers
|
||||
*
|
||||
* @return CacheServerConfiguration[]
|
||||
*/
|
||||
public static function getCacheServers(): array
|
||||
{
|
||||
$results = [];
|
||||
foreach(self::getConfiguration()['cache_system']['servers'] as $server_name => $server)
|
||||
{
|
||||
if($server['enabled'] === true)
|
||||
{
|
||||
$results[] = new CacheServerConfiguration($server_name, $server);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes\Configuration;
|
||||
|
||||
class CacheServerConfiguration
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $enabled;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $host;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private ?int $port;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $driver;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private ?int $priority;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $username;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $password;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $database;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private ?int $reconnect_interval;
|
||||
|
||||
/**
|
||||
* CacheServerConfiguration constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct(string $name, array $configuration)
|
||||
{
|
||||
$this->name = $configuration['name'] ?? $name;
|
||||
$this->enabled = $configuration['enabled'] ?? false;
|
||||
$this->host = $configuration['host'] ?? null;
|
||||
$this->port = $configuration['port'] ?? null;
|
||||
$this->driver = $configuration['driver'] ?? null;
|
||||
$this->priority = $configuration['priority'] ?? null;
|
||||
$this->username = $configuration['username'] ?? null;
|
||||
$this->password = $configuration['password'] ?? null;
|
||||
$this->database = $configuration['database'] ?? null;
|
||||
$this->reconnect_interval = $configuration['reconnect_interval'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the cache server
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cache server is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getEnabled(): bool
|
||||
{
|
||||
return $this->enabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host of the cache server
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHost(): ?string
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPort(): ?int
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDriver(): ?string
|
||||
{
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPriority(): ?int
|
||||
{
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDatabase(): ?string
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getReconnectInterval(): ?int
|
||||
{
|
||||
return $this->reconnect_interval;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes\Configuration;
|
||||
|
||||
class CacheSystemConfiguration
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $client_objects_enabled;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $client_objects_ttl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $client_objects_server_preference;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $client_objects_server_fallback;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $peer_objects_enabled;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $peer_objects_ttl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $peer_objects_server_preference;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $peer_objects_server_fallback;
|
||||
|
||||
/**
|
||||
* CacheSystemConfiguration constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct(array $configuration)
|
||||
{
|
||||
$this->client_objects_enabled = $configuration['cache_system.cache.client_objects_enabled'] ?? false;
|
||||
$this->client_objects_ttl = $configuration['cache_system.cache.client_objects_ttl'] ?? 200;
|
||||
$this->client_objects_server_preference = $configuration['cache_system.cache.client_objects_server_preference'] ?? 'any';
|
||||
$this->client_objects_server_fallback = $configuration['cache_system.cache.client_objects_server_fallback'] ?? 'any';
|
||||
|
||||
$this->peer_objects_enabled = $configuration['cache_system.cache.peer_objects_enabled'] ?? false;
|
||||
$this->peer_objects_ttl = $configuration['cache_system.cache.peer_objects_ttl'] ?? 200;
|
||||
$this->peer_objects_server_preference = $configuration['cache_system.cache.peer_objects_server_preference'] ?? 'any';
|
||||
$this->peer_objects_server_fallback = $configuration['cache_system.cache.peer_objects_server_fallback'] ?? 'any';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if client cache objects are enabled, false otherwise
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getClientObjectsEnabled(): bool
|
||||
{
|
||||
return $this->client_objects_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TTL for client cache objects
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getClientObjectsTtl(): int
|
||||
{
|
||||
return $this->client_objects_ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server preference to where client cache objects should be stored
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientObjectsServerPreference(): string
|
||||
{
|
||||
return $this->client_objects_server_preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server fallback to where client cache objects should be stored
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientObjectsServerFallback(): string
|
||||
{
|
||||
return $this->client_objects_server_fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if peer cache objects are enabled, false otherwise
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getPeerObjectsEnabled(): bool
|
||||
{
|
||||
return $this->peer_objects_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TTL for peer cache objects
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPeerObjectsTtl(): int
|
||||
{
|
||||
return $this->peer_objects_ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server preference to where peer cache objects should be stored
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPeerObjectsServerPreference(): string
|
||||
{
|
||||
return $this->peer_objects_server_preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server fallback to where peer cache objects should be stored
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPeerObjectsServerFallback(): string
|
||||
{
|
||||
return $this->peer_objects_server_fallback;
|
||||
}
|
||||
}
|
6082
src/FederationLib/Classes/Data/wordlist.txt
Normal file
6082
src/FederationLib/Classes/Data/wordlist.txt
Normal file
File diff suppressed because it is too large
Load diff
88
src/FederationLib/Classes/Database.php
Normal file
88
src/FederationLib/Classes/Database.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Logging\Middleware;
|
||||
use Exception;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use LogLib\Log;
|
||||
use LogLib\Psr;
|
||||
|
||||
class Database
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private static $sql_last_connection_time;
|
||||
|
||||
/**
|
||||
* @var Connection|null
|
||||
*/
|
||||
private static $sql_connection;
|
||||
|
||||
/**
|
||||
* Returns/Establishes a connection to the database.
|
||||
*
|
||||
* @return Connection
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public static function getConnection(): Connection
|
||||
{
|
||||
if(self::$sql_connection === null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log::info('net.nosial.federationlib', sprintf('Connecting to the database: %s://%s@%s:%s/%s', Configuration::getDatabaseDriver(), Configuration::getDatabaseUsername(), Configuration::getDatabaseHost(), Configuration::getDatabasePort(), Configuration::getDatabaseName()));
|
||||
$connection = DriverManager::getConnection([
|
||||
'driver' => Configuration::getDatabaseDriver(),
|
||||
'host' => Configuration::getDatabaseHost(),
|
||||
'port' => Configuration::getDatabasePort(),
|
||||
'dbname' => Configuration::getDatabaseName(),
|
||||
'user' => Configuration::getDatabaseUsername(),
|
||||
'password' => Configuration::getDatabasePassword(),
|
||||
'charset' => Configuration::getDatabaseCharset()
|
||||
]);
|
||||
|
||||
$connection->getConfiguration()->setMiddlewares([new Middleware(new Psr('com.dbal.doctrine'))]);
|
||||
$connection->connect();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to connect to the database: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
self::$sql_connection = $connection;
|
||||
self::$sql_last_connection_time = time();
|
||||
}
|
||||
else
|
||||
{
|
||||
/** @noinspection NestedPositiveIfStatementsInspection */
|
||||
if(time() - self::$sql_last_connection_time > Configuration::getDatabaseReconnectInterval())
|
||||
{
|
||||
Log::info('net.nosial.federationlib', sprintf('Interval to reconnect to the %s server has been reached, reconnecting...', Configuration::getDatabaseDriver()));
|
||||
|
||||
// Reconnect to the database.
|
||||
self::$sql_connection->close();
|
||||
self::$sql_connection = null;
|
||||
|
||||
return self::getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$sql_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time of the last connection to the database. (null if no connection has been made)
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public static function getSqlLastConnectionTime(): ?int
|
||||
{
|
||||
return self::$sql_last_connection_time;
|
||||
}
|
||||
}
|
18
src/FederationLib/Classes/Memcached.php
Normal file
18
src/FederationLib/Classes/Memcached.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
class Memcached
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private static $memcached_last_connection_time;
|
||||
|
||||
/**
|
||||
* @var \Memcached|null
|
||||
*/
|
||||
private static $memcached_connection;
|
||||
|
||||
|
||||
}
|
91
src/FederationLib/Classes/Redis.php
Normal file
91
src/FederationLib/Classes/Redis.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use Exception;
|
||||
use LogLib\Log;
|
||||
use RedisException;
|
||||
|
||||
class Redis
|
||||
{
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private static $redis_last_connection_time;
|
||||
|
||||
/**
|
||||
* @var \Redis|null
|
||||
*/
|
||||
private static $redis_connection;
|
||||
|
||||
/**
|
||||
* Returns/Establishes a connection to the redis server.
|
||||
* Returns null if redis is disabled.
|
||||
*
|
||||
* @return \Redis|null
|
||||
* @throws RedisException
|
||||
*/
|
||||
public static function getConnection(): ?\Redis
|
||||
{
|
||||
if(!Configuration::isRedisEnabled())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(self::$redis_connection === null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log::info('net.nosial.federationlib', sprintf('Connecting to the redis server: %s:%s', Configuration::getRedisHost(), Configuration::getRedisPort()));
|
||||
$redis = new \Redis();
|
||||
$redis->connect(Configuration::getRedisHost(), Configuration::getRedisPort());
|
||||
if(Configuration::getRedisPassword() !== null)
|
||||
{
|
||||
$redis->auth(Configuration::getRedisPassword());
|
||||
}
|
||||
$redis->select(Configuration::getRedisDatabase());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new RedisException('Failed to connect to the redis server: ' . $e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
self::$redis_connection = $redis;
|
||||
self::$redis_last_connection_time = time();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(self::$redis_last_connection_time === null || self::$redis_last_connection_time < (time() - Configuration::getRedisReconnectInterval()))
|
||||
{
|
||||
Log::info('net.nosial.federationlib', 'Interval to reconnect to the redis server has been reached, reconnecting...');
|
||||
|
||||
try
|
||||
{
|
||||
self::$redis_connection->close();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// Do nothing
|
||||
unset($e);
|
||||
}
|
||||
|
||||
self::$redis_connection = null;
|
||||
return self::getConnection();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$redis_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last time the redis server was connected to.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public static function getRedisLastConnectionTime(): ?int
|
||||
{
|
||||
return self::$redis_last_connection_time;
|
||||
}
|
||||
}
|
102
src/FederationLib/Classes/Security.php
Normal file
102
src/FederationLib/Classes/Security.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
class Security
|
||||
{
|
||||
/**
|
||||
* The number of digits in the generated code.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $digits = 6;
|
||||
|
||||
/**
|
||||
* The number of seconds a code is valid.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $time_step = 30;
|
||||
|
||||
/**
|
||||
* The number of seconds the clock is allowed to drift.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $time_offset = 0;
|
||||
|
||||
/**
|
||||
* The hash algorithm used to generate the code.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $hash_algorithm = 'sha1';
|
||||
|
||||
/**
|
||||
* Generates a secret TOTP key.
|
||||
*
|
||||
* @param $length
|
||||
* @return string
|
||||
*/
|
||||
public static function generateSecret($length = 20): string
|
||||
{
|
||||
try
|
||||
{
|
||||
return Base32::encode(random_bytes($length));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new RuntimeException('Failed to generate secret: ' . $e->getMessage(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a TOTP code based on a secret and timestamp.
|
||||
*
|
||||
* @param $secret
|
||||
* @param $timestamp
|
||||
* @return string
|
||||
*/
|
||||
public static function generateCode($secret, $timestamp=null): string
|
||||
{
|
||||
if (!$timestamp)
|
||||
{
|
||||
$timestamp = time();
|
||||
}
|
||||
|
||||
$hash = hash_hmac(self::$hash_algorithm, pack('N*', 0, $timestamp / self::$time_step + self::$time_offset), Base32::decode($secret), true);
|
||||
$offset = ord(substr($hash, -1)) & 0x0F;
|
||||
$value = unpack('N', substr($hash, $offset, 4))[1] & 0x7FFFFFFF;
|
||||
$modulo = 10 ** self::$digits;
|
||||
return str_pad($value % $modulo, self::$digits, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a TOTP code.
|
||||
*
|
||||
* @param $secret
|
||||
* @param $code
|
||||
* @param $window
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateCode($secret, $code, $window = 1): bool
|
||||
{
|
||||
$timestamp = time();
|
||||
|
||||
for ($i = -$window; $i <= $window; $i++)
|
||||
{
|
||||
$generatedCode = self::generateCode($secret, $timestamp + $i * self::$time_step);
|
||||
if ($code === $generatedCode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
183
src/FederationLib/Classes/Utilities.php
Normal file
183
src/FederationLib/Classes/Utilities.php
Normal file
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use FederationLib\Enums\SerializationMethod;
|
||||
use FederationLib\Interfaces\SerializableObjectInterface;
|
||||
use InvalidArgumentException;
|
||||
use LogLib\Log;
|
||||
use Throwable;
|
||||
|
||||
class Utilities
|
||||
{
|
||||
/**
|
||||
* @var string[]|null
|
||||
*/
|
||||
private static $wordlist;
|
||||
|
||||
/**
|
||||
* Returns an array of words from the wordlist.
|
||||
*
|
||||
* @param bool $cache True to cache the wordlist in memory, false to not cache the wordlist
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getWordlist(bool $cache=true): array
|
||||
{
|
||||
if(self::$wordlist !== null)
|
||||
{
|
||||
return self::$wordlist;
|
||||
}
|
||||
|
||||
$wordlist = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR . 'wordlist.txt');
|
||||
$wordlist = array_filter(array_map('trim', explode("\n", $wordlist)));
|
||||
|
||||
if($cache)
|
||||
{
|
||||
self::$wordlist = $wordlist;
|
||||
}
|
||||
|
||||
return $wordlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random name.
|
||||
*
|
||||
* @param int $word_count
|
||||
* @param string $separator
|
||||
* @param bool $capitalize
|
||||
* @return string
|
||||
*/
|
||||
public static function generateName(int $word_count=3, string $separator=' ', bool $capitalize=true): string
|
||||
{
|
||||
$wordlist = self::getWordlist();
|
||||
$name = '';
|
||||
|
||||
for($i = 0; $i < $word_count; $i++)
|
||||
{
|
||||
$name .= $wordlist[array_rand($wordlist)] . $separator;
|
||||
}
|
||||
|
||||
$name = substr($name, 0, -1);
|
||||
|
||||
if($capitalize)
|
||||
{
|
||||
$name = ucwords($name);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a federated address into an array of parts.
|
||||
* Example: entity:uid
|
||||
*
|
||||
* @param string $address
|
||||
* @return array
|
||||
*/
|
||||
public static function parseFederatedAddress(string $address): array
|
||||
{
|
||||
if (preg_match($address, '/^(?P<entity>[a-zA-Z0-9_-]+):(?P<uid>[a-zA-Z0-9_-]+)$/', $matches, PREG_UNMATCHED_AS_NULL))
|
||||
{
|
||||
return [
|
||||
'entity' => $matches['entity'],
|
||||
'uid' => $matches['uid']
|
||||
];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('Invalid address provided: %s', $address));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes an array into a string.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $method
|
||||
* @return string
|
||||
*/
|
||||
public static function serialize(array|SerializableObjectInterface $data, string $method): string
|
||||
{
|
||||
if($data instanceof SerializableObjectInterface)
|
||||
{
|
||||
$data = $data->toArray();
|
||||
}
|
||||
|
||||
switch(strtolower($method))
|
||||
{
|
||||
case SerializationMethod::JSON:
|
||||
return json_encode($data);
|
||||
|
||||
case SerializationMethod::MSGPACK:
|
||||
return msgpack_pack($data);
|
||||
|
||||
default:
|
||||
Log::warning('net.nosial.federationlib', sprintf('Unknown serialization method: %s, defaulting to msgpack', $method));
|
||||
return msgpack_pack($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts a Throwable into an array representation.
|
||||
*
|
||||
* @param Throwable $throwable
|
||||
* @return array
|
||||
*/
|
||||
public static function throwableToArray(Throwable $throwable): array
|
||||
{
|
||||
$results = [
|
||||
'message' => $throwable->getMessage(),
|
||||
'code' => $throwable->getCode(),
|
||||
'file' => $throwable->getFile(),
|
||||
'line' => $throwable->getLine(),
|
||||
'trace' => $throwable->getTrace(),
|
||||
'previous' => $throwable->getPrevious()
|
||||
];
|
||||
|
||||
if($results['previous'] instanceof Throwable)
|
||||
{
|
||||
$results['previous'] = self::throwableToArray($results['previous']);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the z-score method to detect anomalies in an array of numbers.
|
||||
* The key of the returned array is the index of the number in the original array.
|
||||
* The value of the returned array is the probability of the number being an anomaly.
|
||||
* Negative values are anomalies, positive values are not.
|
||||
* The higher the absolute value, the more likely it is to be an anomaly.
|
||||
*
|
||||
* @param array $data An array of numbers to check for anomalies
|
||||
* @param int $threshold The threshold to use for detecting anomalies
|
||||
* @param bool $filter True to only return anomalies, false to return all values
|
||||
* @return array An array of probabilities
|
||||
*/
|
||||
public static function detectAnomalies(array $data, int $threshold = 2, bool $filter=true): array
|
||||
{
|
||||
$mean = array_sum($data) / count($data);
|
||||
$squares = array_map(static function($x) use ($mean) { return ($x - $mean) ** 2; }, $data);
|
||||
$standard_deviation = sqrt(array_sum($squares) / count($data));
|
||||
$outliers = [];
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
$z_score = ($value - $mean) / $standard_deviation;
|
||||
$probability = exp(-$z_score ** 2 / 2) / (sqrt(2 * M_PI) * $standard_deviation);
|
||||
|
||||
if($filter)
|
||||
{
|
||||
if ($z_score >= $threshold)
|
||||
{
|
||||
$outliers[$key] = -$probability;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$outliers[$key] = $probability;
|
||||
}
|
||||
|
||||
}
|
||||
return $outliers;
|
||||
}
|
||||
}
|
60
src/FederationLib/Classes/Validate.php
Normal file
60
src/FederationLib/Classes/Validate.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Classes;
|
||||
|
||||
use FederationLib\Enums\Standard\PeerType;
|
||||
use FederationLib\Enums\Standard\InternetPeerType;
|
||||
use FederationLib\Enums\Standard\PeerAssociationType;
|
||||
use FederationLib\Enums\Standard\UserPeerType;
|
||||
|
||||
class Validate
|
||||
{
|
||||
/**
|
||||
* Determines the entity type based on the entity type string.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @return string
|
||||
*/
|
||||
public static function getEntityType(string $entity_type): string
|
||||
{
|
||||
if(in_array($entity_type, InternetPeerType::ALL))
|
||||
{
|
||||
return PeerType::INTERNET;
|
||||
}
|
||||
|
||||
if(in_array($entity_type, UserPeerType::ALL))
|
||||
{
|
||||
return PeerType::USER;
|
||||
}
|
||||
|
||||
return PeerType::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the entity type is valid and supported.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateEntityType(string $entity_type): bool
|
||||
{
|
||||
if(self::getEntityType($entity_type) === PeerType::UNKNOWN)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the peer association type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public static function peerAssociationType(string $type): bool
|
||||
{
|
||||
if(in_array(strtolower($type), PeerAssociationType::ALL))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
9
src/FederationLib/Enums/CacheDriver.php
Normal file
9
src/FederationLib/Enums/CacheDriver.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class CacheDriver
|
||||
{
|
||||
public const MEMCACHED = 'memcached';
|
||||
public const REDIS = 'redis';
|
||||
}
|
30
src/FederationLib/Enums/DatabaseTables.php
Normal file
30
src/FederationLib/Enums/DatabaseTables.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class DatabaseTables
|
||||
{
|
||||
public const ANOMALY_TRACKING = 'anomaly_tracking';
|
||||
public const ASSOCIATIONS = 'associations';
|
||||
public const CLIENTS = 'clients';
|
||||
public const EVENTS = 'events';
|
||||
public const PEERS = 'peers';
|
||||
public const PEERS_TELEGRAM_CHAT = 'peers_telegram_chat';
|
||||
public const PEERS_TELEGRAM_USER = 'peers_telegram_user';
|
||||
public const QUERY_DOCUMENTS = 'query_documents';
|
||||
public const REPORTS = 'reports';
|
||||
public const RESTRICTIONS = 'restrictions';
|
||||
|
||||
public const ALL = [
|
||||
self::ANOMALY_TRACKING,
|
||||
self::ASSOCIATIONS,
|
||||
self::CLIENTS,
|
||||
self::EVENTS,
|
||||
self::PEERS,
|
||||
self::PEERS_TELEGRAM_CHAT,
|
||||
self::PEERS_TELEGRAM_USER,
|
||||
self::QUERY_DOCUMENTS,
|
||||
self::REPORTS,
|
||||
self::RESTRICTIONS,
|
||||
];
|
||||
}
|
11
src/FederationLib/Enums/EventPriority.php
Normal file
11
src/FederationLib/Enums/EventPriority.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class EventPriority
|
||||
{
|
||||
public const NONE = 0;
|
||||
public const LOW = 1;
|
||||
public const MEDIUM = 2;
|
||||
public const HIGH = 3;
|
||||
}
|
9
src/FederationLib/Enums/FilterOrder.php
Normal file
9
src/FederationLib/Enums/FilterOrder.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class FilterOrder
|
||||
{
|
||||
public const ASCENDING = 'asc';
|
||||
public const DESCENDING = 'desc';
|
||||
}
|
18
src/FederationLib/Enums/Filters/ListClientsFilter.php
Normal file
18
src/FederationLib/Enums/Filters/ListClientsFilter.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Filters;
|
||||
|
||||
final class ListClientsFilter
|
||||
{
|
||||
public const CREATED_TIMESTAMP = 'created_timestamp';
|
||||
public const UPDATED_TIMESTAMP = 'updated_timestamp';
|
||||
public const SEEN_TIMESTAMP = 'seen_timestamp';
|
||||
public const ENABLED = 'enabled';
|
||||
|
||||
public const ALL = [
|
||||
self::CREATED_TIMESTAMP,
|
||||
self::UPDATED_TIMESTAMP,
|
||||
self::SEEN_TIMESTAMP,
|
||||
self::ENABLED
|
||||
];
|
||||
}
|
8
src/FederationLib/Enums/Misc.php
Normal file
8
src/FederationLib/Enums/Misc.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class Misc
|
||||
{
|
||||
public const FEDERATIONLIB = 'net.nosial.federationlib';
|
||||
}
|
9
src/FederationLib/Enums/SerializationMethod.php
Normal file
9
src/FederationLib/Enums/SerializationMethod.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class SerializationMethod
|
||||
{
|
||||
public const JSON = 'json';
|
||||
public const MSGPACK = 'msgpack';
|
||||
}
|
12
src/FederationLib/Enums/Standard/AttachmentDataType.php
Normal file
12
src/FederationLib/Enums/Standard/AttachmentDataType.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class AttachmentDataType
|
||||
{
|
||||
public const RAW = 'raw';
|
||||
|
||||
public const BASE64 = 'base64';
|
||||
|
||||
public const URL = 'url';
|
||||
}
|
15
src/FederationLib/Enums/Standard/ContentType.php
Normal file
15
src/FederationLib/Enums/Standard/ContentType.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class ContentType
|
||||
{
|
||||
public const NULL = 'null';
|
||||
public const TEXT = 'text/plain';
|
||||
public const HTML = 'text/html';
|
||||
public const JSON = 'application/json';
|
||||
public const XML = 'application/xml';
|
||||
public const EMAIL = 'message/rfc822';
|
||||
public const BASE64 = 'application/base64';
|
||||
public const HTTP = 'application/http';
|
||||
}
|
12
src/FederationLib/Enums/Standard/DocumentSubjectType.php
Normal file
12
src/FederationLib/Enums/Standard/DocumentSubjectType.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class DocumentSubjectType
|
||||
{
|
||||
public const RECON = 'RECON';
|
||||
|
||||
public const ALL = [
|
||||
self::RECON
|
||||
];
|
||||
}
|
55
src/FederationLib/Enums/Standard/ErrorCodes.php
Normal file
55
src/FederationLib/Enums/Standard/ErrorCodes.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class ErrorCodes
|
||||
{
|
||||
/**
|
||||
* An internal server error occurred.
|
||||
*/
|
||||
public const INTERNAL_SERVER_ERROR = 0;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The requested client was not found.
|
||||
*/
|
||||
public const CLIENT_NOT_FOUND = 1000;
|
||||
|
||||
/**
|
||||
* The requested client name is invalid.
|
||||
*/
|
||||
public const INVALID_CLIENT_NAME = 1001;
|
||||
|
||||
/**
|
||||
* The requested client description is invalid.
|
||||
*/
|
||||
public const INVALID_CLIENT_DESCRIPTION = 1002;
|
||||
|
||||
/**
|
||||
* The requested client signature verification failed.
|
||||
*/
|
||||
public const SIGNATURE_VERIFICATION_FAILED = 1003;
|
||||
|
||||
/**
|
||||
* The requested client is disabled.
|
||||
*/
|
||||
public const CLIENT_DISABLED = 1004;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The requested user entity was not found.
|
||||
*/
|
||||
public const PEER_NOT_FOUND = 2000;
|
||||
|
||||
/**
|
||||
* The requested peer association was not found.
|
||||
*/
|
||||
public const PEER_ASSOCIATION_NOT_FOUND = 3000;
|
||||
|
||||
/**
|
||||
* The requested peer association type is invalid.
|
||||
*/
|
||||
public const INVALID_PEER_ASSOCIATION_TYPE = 3001;
|
||||
}
|
116
src/FederationLib/Enums/Standard/EventCode.php
Normal file
116
src/FederationLib/Enums/Standard/EventCode.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class EventCode
|
||||
{
|
||||
/**
|
||||
* A generic event code indicates the event is not specific to a particular event type.
|
||||
* This could apply to server events such as cleaning up old data or general system
|
||||
* messages.
|
||||
*/
|
||||
public const GENERIC = 'GENERIC';
|
||||
|
||||
/**
|
||||
* Indicates a client was created.
|
||||
*/
|
||||
public const CLIENT_CREATED = 'CLIENT_CREATED';
|
||||
|
||||
/**
|
||||
* Indicates a client was deleted.
|
||||
*/
|
||||
public const CLIENT_DELETED = 'CLIENT_DELETED';
|
||||
|
||||
/**
|
||||
* Indicates a client was enabled.
|
||||
*/
|
||||
public const CLIENT_ENABLED = 'CLIENT_ENABLED';
|
||||
|
||||
/**
|
||||
* Indicates a client was disabled.
|
||||
*/
|
||||
public const CLIENT_DISABLED = 'CLIENT_DISABLED';
|
||||
|
||||
/**
|
||||
* Indicates a client's authentication was enabled.
|
||||
*/
|
||||
public const CLIENT_AUTHENTICATION_ENABLED = 'CLIENT_AUTHENTICATION_ENABLED';
|
||||
|
||||
/**
|
||||
* Indicates a client's authentication was disabled.
|
||||
*/
|
||||
public const CLIENT_AUTHENTICATION_DISABLED = 'CLIENT_AUTHENTICATION_DISABLED';
|
||||
|
||||
/**
|
||||
* Indicates a client's query document was rejected
|
||||
* (e.g. the query document couldn't be processed by the server, see the message for details).
|
||||
*/
|
||||
public const QUERY_DOCUMENT_REJECTED = 'QUERY_DOCUMENT_REJECTED';
|
||||
|
||||
/**
|
||||
* Indicates an anomaly was detected in INCOMING events
|
||||
* (e.g. a client is receiving too many requests/messages).
|
||||
*/
|
||||
public const ANOMALY_INCOMING = 'ANOMALY_INCOMING';
|
||||
|
||||
/**
|
||||
* Indicates an anomaly was detected in OUTGOING events
|
||||
* (e.g. a client is sending too many requests/messages).
|
||||
*/
|
||||
public const ANOMALY_OUTGOING = 'ANOMALY_OUTGOING';
|
||||
|
||||
/**
|
||||
* Indicates an anomaly was detected in PEER JOIN events
|
||||
* (e.g. a channel is receiving too many join requests/events).
|
||||
*/
|
||||
public const ANOMALY_PEER_JOIN = 'ANOMALY_PEER_JOIN';
|
||||
|
||||
/**
|
||||
* Indicates a service cleanup event.
|
||||
* This is a special event code that is not intended to be used by clients.
|
||||
* (e.g. the server is cleaning up old data, results are explained in the message).
|
||||
*/
|
||||
public const SERVICE_CLEANUP = 'SERVICE_CLEANUP';
|
||||
|
||||
/**
|
||||
* Indicates a service message event.
|
||||
* This is a special event code that is not intended to be used by clients.
|
||||
* (e.g. a message from the server).
|
||||
*/
|
||||
public const SERVICE_MESSAGE = 'SERVICE_MESSAGE';
|
||||
|
||||
/**
|
||||
* Indicates a service error event.
|
||||
* This is a special event code that is not intended to be used by clients.
|
||||
* (e.g. an error occurred on the server, results are explained in the message).
|
||||
*/
|
||||
public const SERVICE_ERROR = 'SERVICE_ERROR';
|
||||
|
||||
/**
|
||||
* Indicates a peer was restricted by the server.
|
||||
* This is a special event code that is not intended to be used by clients.
|
||||
* (e.g. a peer was restricted by the server, results are explained in the message).
|
||||
*/
|
||||
public const PEER_RESTRICTED = 'PEER_RESTRICTED';
|
||||
|
||||
/**
|
||||
* An array of all event codes.
|
||||
*/
|
||||
public const ALL = [
|
||||
self::GENERIC,
|
||||
self::CLIENT_CREATED,
|
||||
self::CLIENT_DELETED,
|
||||
self::CLIENT_ENABLED,
|
||||
self::CLIENT_DISABLED,
|
||||
self::CLIENT_AUTHENTICATION_ENABLED,
|
||||
self::CLIENT_AUTHENTICATION_DISABLED,
|
||||
self::QUERY_DOCUMENT_REJECTED,
|
||||
self::ANOMALY_INCOMING,
|
||||
self::ANOMALY_OUTGOING,
|
||||
self::ANOMALY_PEER_JOIN,
|
||||
self::SERVICE_CLEANUP,
|
||||
self::SERVICE_MESSAGE,
|
||||
self::SERVICE_ERROR,
|
||||
self::PEER_RESTRICTED
|
||||
];
|
||||
}
|
16
src/FederationLib/Enums/Standard/EventType.php
Normal file
16
src/FederationLib/Enums/Standard/EventType.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class EventType
|
||||
{
|
||||
public const INFORMATION = 'INF';
|
||||
public const WARNING = 'WRN';
|
||||
public const ERROR = 'ERR';
|
||||
|
||||
public const ALL = [
|
||||
self::INFORMATION,
|
||||
self::WARNING,
|
||||
self::ERROR
|
||||
];
|
||||
}
|
15
src/FederationLib/Enums/Standard/InternetPeerType.php
Normal file
15
src/FederationLib/Enums/Standard/InternetPeerType.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class InternetPeerType
|
||||
{
|
||||
public const TELEGRAM_CHAT = 'telegram.chat';
|
||||
|
||||
public const TELEGRAM_CHANNEL = 'telegram.channel';
|
||||
|
||||
public const ALL = [
|
||||
self::TELEGRAM_CHAT,
|
||||
self::TELEGRAM_CHANNEL,
|
||||
];
|
||||
}
|
48
src/FederationLib/Enums/Standard/PeerAssociationType.php
Normal file
48
src/FederationLib/Enums/Standard/PeerAssociationType.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class PeerAssociationType
|
||||
{
|
||||
/**
|
||||
* Indicates the parent peer is the owner of the child peer.
|
||||
*/
|
||||
public const OWNER = 'owner';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is the administrator of the child peer.
|
||||
*/
|
||||
public const ADMIN = 'admin';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is a moderator of the child peer.
|
||||
*/
|
||||
public const MODERATOR = 'moderator';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is a member of the child peer.
|
||||
*/
|
||||
public const MEMBER = 'member';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is banned from the child peer.
|
||||
*/
|
||||
public const BANNED = 'banned';
|
||||
|
||||
/**
|
||||
* Indicates the parent peer is an alternative address for the child peer.
|
||||
*/
|
||||
public const ALTERNATIVE = 'alternative';
|
||||
|
||||
/**
|
||||
* An array of all peer association types.
|
||||
*/
|
||||
const ALL = [
|
||||
self::OWNER,
|
||||
self::ADMIN,
|
||||
self::MODERATOR,
|
||||
self::MEMBER,
|
||||
self::BANNED,
|
||||
self::ALTERNATIVE
|
||||
];
|
||||
}
|
18
src/FederationLib/Enums/Standard/PeerType.php
Normal file
18
src/FederationLib/Enums/Standard/PeerType.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class PeerType
|
||||
{
|
||||
const USER = 'user';
|
||||
|
||||
const INTERNET = 'internet';
|
||||
|
||||
const UNKNOWN = 'unknown';
|
||||
|
||||
const ALL = [
|
||||
self::USER,
|
||||
self::INTERNET,
|
||||
self::UNKNOWN
|
||||
];
|
||||
}
|
76
src/FederationLib/Enums/Standard/ReportType.php
Normal file
76
src/FederationLib/Enums/Standard/ReportType.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class ReportType
|
||||
{
|
||||
/**
|
||||
* This category can be used for general reports submitted by users regarding any issues they encounter on the platform.
|
||||
*/
|
||||
public const USER_REPORT = 'user_report';
|
||||
|
||||
/**
|
||||
* This category can be used when reports are received from external sources, such as third-party organizations or security researchers.
|
||||
*/
|
||||
public const EXTERNAL_REPORT = 'external_report';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting unsolicited and unwanted commercial or promotional content.
|
||||
*/
|
||||
public const SPAM = 'spam';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting fraudulent activities, schemes, or attempts to deceive users for personal gain.
|
||||
*/
|
||||
public const SCAM = 'scam';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting attempts to acquire sensitive user information, such as passwords or financial details, through deceptive means.
|
||||
*/
|
||||
public const PHISHING = 'phishing';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting the presence or distribution of malicious software or code.
|
||||
*/
|
||||
public const MALWARE = 'malware';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting instances of unauthorized distribution or infringement of copyrighted material.
|
||||
*/
|
||||
public const PIRACY = 'piracy';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting the presence or activities of botnets, which are networks of compromised computers used for malicious purposes.
|
||||
*/
|
||||
public const BOTNET = 'botnet';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting the presence of explicit adult content that violates the platform's guidelines or policies.
|
||||
*/
|
||||
public const PORNOGRAPHY = 'pornography';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting the presence of graphic and violent content that may be disturbing or offensive.
|
||||
*/
|
||||
public const GORE = 'gore';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting any abuse or misuse of the platform's services or features.
|
||||
*/
|
||||
public const SERVICE_ABUSE = 'service_abuse';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting instances of child abuse, child pornography, or any content that exploits minors.
|
||||
*/
|
||||
public const CHILD_EXPLOITATION = 'child_exploitation';
|
||||
|
||||
/**
|
||||
* This category can be used for reporting fraudulent activities conducted online, such as scams, fake websites, or misleading online marketplaces.
|
||||
*/
|
||||
public const FRAUD = 'fraud';
|
||||
|
||||
/**
|
||||
* This category can be used for any reports that do not fit into the predefined categories but still require attention.
|
||||
*/
|
||||
public const OTHER = 'other';
|
||||
}
|
12
src/FederationLib/Enums/Standard/ScanMode.php
Normal file
12
src/FederationLib/Enums/Standard/ScanMode.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class ScanMode
|
||||
{
|
||||
/**
|
||||
* Means that the scanner should only scan the header of the document.
|
||||
*/
|
||||
public const DOCUMENT = 'document';
|
||||
|
||||
}
|
12
src/FederationLib/Enums/Standard/UserPeerType.php
Normal file
12
src/FederationLib/Enums/Standard/UserPeerType.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums\Standard;
|
||||
|
||||
final class UserPeerType
|
||||
{
|
||||
public const TELEGRAM_USER = 'telegram.user';
|
||||
|
||||
public const ALL = [
|
||||
self::TELEGRAM_USER,
|
||||
];
|
||||
}
|
8
src/FederationLib/Enums/UserEntityRelationType.php
Normal file
8
src/FederationLib/Enums/UserEntityRelationType.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Enums;
|
||||
|
||||
final class UserEntityRelationType
|
||||
{
|
||||
public const OWNER = 'owner';
|
||||
}
|
26
src/FederationLib/Exceptions/DatabaseException.php
Normal file
26
src/FederationLib/Exceptions/DatabaseException.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class DatabaseException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
* @noinspection SenselessProxyMethodInspection
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
if (!empty($previous))
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous);
|
||||
}
|
||||
else
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
}
|
20
src/FederationLib/Exceptions/RedisException.php
Normal file
20
src/FederationLib/Exceptions/RedisException.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class RedisException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Throwable|null $previous
|
||||
* @noinspection SenselessProxyMethodInspection
|
||||
*/
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class ClientNotFoundException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::CLIENT_NOT_FOUND, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Enums\Standard\ErrorCodes;
|
||||
use Throwable;
|
||||
|
||||
class InvalidClientNameException extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, ErrorCodes::INVALID_CLIENT_NAME, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class PeerNotFoundException extends Exception
|
||||
{
|
||||
/**
|
||||
* PeerNotFoundException constructor.
|
||||
*
|
||||
* @param string $peer
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $peer, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("The peer '{$peer}' was not found", 0, $previous);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions\Standard;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class UnsupportedPeerType extends Exception
|
||||
{
|
||||
/**
|
||||
* UnsupportedPeerType constructor.
|
||||
*
|
||||
* @param string $peer_type
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $peer_type, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct("The peer type '{$peer_type}' is not supported by this server", 0, $previous);
|
||||
}
|
||||
}
|
19
src/FederationLib/Exceptions/UnsupportedEntityType.php
Normal file
19
src/FederationLib/Exceptions/UnsupportedEntityType.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class UnsupportedEntityType extends Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
38
src/FederationLib/FederationLib.php
Normal file → Executable file
38
src/FederationLib/FederationLib.php
Normal file → Executable file
|
@ -2,7 +2,45 @@
|
|||
|
||||
namespace FederationLib;
|
||||
|
||||
use FederationLib\Managers\ClientManager;
|
||||
use FederationLib\Managers\EventLogManager;
|
||||
|
||||
class FederationLib
|
||||
{
|
||||
/**
|
||||
* @var ClientManager
|
||||
*/
|
||||
private ClientManager $client_manager;
|
||||
|
||||
/**
|
||||
* @var EventLogManager
|
||||
*/
|
||||
private EventLogManager $event_log_manager;
|
||||
|
||||
/**
|
||||
* FederationLib constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->client_manager = new ClientManager($this);
|
||||
$this->event_log_manager = new EventLogManager($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Client manager instance
|
||||
*
|
||||
* @return ClientManager
|
||||
*/
|
||||
public function getClientManager(): ClientManager
|
||||
{
|
||||
return $this->client_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EventLogManager
|
||||
*/
|
||||
public function getEventLogManager(): EventLogManager
|
||||
{
|
||||
return $this->event_log_manager;
|
||||
}
|
||||
}
|
47
src/FederationLib/Interfaces/CacheDriverInterface.php
Normal file
47
src/FederationLib/Interfaces/CacheDriverInterface.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Interfaces;
|
||||
|
||||
use FederationLib\Classes\Configuration\CacheServerConfiguration;
|
||||
use FederationLib\Classes\Memcached;
|
||||
use FederationLib\Classes\Redis;
|
||||
|
||||
interface CacheDriverInterface
|
||||
{
|
||||
/**
|
||||
* Constructs a cache server driver
|
||||
*
|
||||
* @param CacheServerConfiguration $configuration
|
||||
*/
|
||||
public function __construct(CacheServerConfiguration $configuration);
|
||||
|
||||
/**
|
||||
* Returns the configuration for the driver
|
||||
*
|
||||
* @return CacheServerConfiguration
|
||||
*/
|
||||
public function getConfiguration(): CacheServerConfiguration;
|
||||
|
||||
/**
|
||||
* Returns the driver connection instance.
|
||||
*
|
||||
* @return Redis|Memcached
|
||||
*/
|
||||
public function getConnection(): Redis|Memcached;
|
||||
|
||||
/**
|
||||
* Connects to the cache server
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function connect(): void;
|
||||
|
||||
/**
|
||||
* Disconnects from the cache server
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disconnect(): void;
|
||||
|
||||
|
||||
}
|
20
src/FederationLib/Interfaces/EntityObjectInterface.php
Normal file
20
src/FederationLib/Interfaces/EntityObjectInterface.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Interfaces;
|
||||
|
||||
interface EntityObjectInterface extends SerializableObjectInterface
|
||||
{
|
||||
/**
|
||||
* Validates the given object and returns true if it is valid.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(): bool;
|
||||
|
||||
/**
|
||||
* Returns the standard federated address of the entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFederatedAddress(): string;
|
||||
}
|
21
src/FederationLib/Interfaces/SerializableObjectInterface.php
Normal file
21
src/FederationLib/Interfaces/SerializableObjectInterface.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Interfaces;
|
||||
|
||||
interface SerializableObjectInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array representation of the object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array;
|
||||
|
||||
/**
|
||||
* Constructs object from an array representation.
|
||||
*
|
||||
* @param array $array
|
||||
* @return SerializableObjectInterface
|
||||
*/
|
||||
public static function fromArray(array $array): SerializableObjectInterface;
|
||||
}
|
503
src/FederationLib/Managers/ClientManager.php
Normal file
503
src/FederationLib/Managers/ClientManager.php
Normal file
|
@ -0,0 +1,503 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Managers;
|
||||
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration;
|
||||
use FederationLib\Classes\Database;
|
||||
use FederationLib\Classes\Redis;
|
||||
use FederationLib\Classes\Utilities;
|
||||
use FederationLib\Enums\DatabaseTables;
|
||||
use FederationLib\Enums\EventPriority;
|
||||
use FederationLib\Enums\FilterOrder;
|
||||
use FederationLib\Enums\Filters\ListClientsFilter;
|
||||
use FederationLib\Enums\Standard\EventCode;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Exceptions\Standard\ClientNotFoundException;
|
||||
use FederationLib\FederationLib;
|
||||
use FederationLib\Objects\Client;
|
||||
use LogLib\Log;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
class ClientManager
|
||||
{
|
||||
/**
|
||||
* @var FederationLib
|
||||
*/
|
||||
private FederationLib $federationLib;
|
||||
|
||||
/**
|
||||
* ClientManager constructor.
|
||||
*
|
||||
* @param FederationLib $federationLib
|
||||
*/
|
||||
public function __construct(FederationLib $federationLib)
|
||||
{
|
||||
$this->federationLib = $federationLib;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a client into the database, returns the UUID that was generated for the client.
|
||||
*
|
||||
* @param Client $client
|
||||
* @return string
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function registerClient(Client $client): string
|
||||
{
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->insert(DatabaseTables::CLIENTS);
|
||||
$uuid = Uuid::v4()->toRfc4122();
|
||||
|
||||
foreach($client->toArray() as $key => $value)
|
||||
{
|
||||
switch($key)
|
||||
{
|
||||
case 'id':
|
||||
$qb->setValue($key, ':' . $key);
|
||||
$qb->setParameter($key, $uuid);
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
if($value === null || strlen($value) === 0 || !preg_match('/^[a-zA-Z0-9_\-]+$/', $value ))
|
||||
{
|
||||
$value = Utilities::generateName(4);
|
||||
Log::debug('net.nosial.federationlib', sprintf('generated name for client: %s', $value));
|
||||
}
|
||||
|
||||
$qb->setValue($key, ':' . $key);
|
||||
$qb->setParameter($key, substr($value, 0, 64));
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
if($value !== null)
|
||||
{
|
||||
$qb->setValue($key, ':' . $key);
|
||||
$qb->setParameter($key, substr($value, 0, 255));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'enabled':
|
||||
$qb->setValue($key, ':' . $key);
|
||||
$qb->setParameter($key, $value ? 1 : 0);
|
||||
break;
|
||||
|
||||
case 'seen_timestamp':
|
||||
case 'updated_timestamp':
|
||||
case 'created_timestamp':
|
||||
$qb->setValue($key, ':' . $key);
|
||||
$qb->setParameter($key, time());
|
||||
break;
|
||||
|
||||
default:
|
||||
$qb->setValue($key, ':' . $key);
|
||||
$qb->setParameter($key, $value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to register client: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
$this->federationLib->getEventLogManager()->logEvent(
|
||||
EventCode::CLIENT_CREATED, EventPriority::LOW, null,
|
||||
sprintf('Registered client with UUID %s', $uuid)
|
||||
);
|
||||
|
||||
Log::info('net.nosial.federationlib', sprintf('Registered client with UUID %s', $uuid));
|
||||
return $uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an existing client from the database.
|
||||
*
|
||||
* @param string|Client $uuid
|
||||
* @return Client
|
||||
* @throws ClientNotFoundException
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function getClient(string|Client $uuid): Client
|
||||
{
|
||||
if($uuid instanceof Client)
|
||||
{
|
||||
$uuid = $uuid->getUuid();
|
||||
}
|
||||
|
||||
if(Configuration::isRedisCacheClientObjectsEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
if(Redis::getConnection()?->exists(sprintf('Client<%s>', $uuid)))
|
||||
{
|
||||
return Client::fromArray(Redis::getConnection()?->hGetAll(sprintf('Client<%s>', $uuid)));
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning('net.nosial.federationlib', sprintf('Failed to get Client from redis: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select('*');
|
||||
$qb->from(DatabaseTables::CLIENTS);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $uuid);
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
|
||||
if($result->rowCount() === 0)
|
||||
{
|
||||
throw new ClientNotFoundException($uuid);
|
||||
}
|
||||
|
||||
$client = Client::fromArray($result->fetchAssociative());
|
||||
}
|
||||
catch(ClientNotFoundException $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to get Client: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if(Configuration::isRedisCacheClientObjectsEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
Redis::getConnection()?->hMSet((string)$client, $client->toArray());
|
||||
Redis::getConnection()?->expire((string)$client, Configuration::getRedisCacheClientObjectsTTL());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning('net.nosial.federationlib', sprintf('Failed to cache Client in redis: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a client record in the database, if the client does not exist it will be created.
|
||||
* This function is cache aware, if the client is cached it will only update the changed values.
|
||||
*
|
||||
* @param Client $client
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function updateClient(Client $client): void
|
||||
{
|
||||
$cached_client = null;
|
||||
|
||||
if(Configuration::isRedisCacheClientObjectsEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
if(Redis::getConnection()?->exists(sprintf('Client<%s>', $client->getUuid())))
|
||||
{
|
||||
$cached_client = Client::fromArray(Redis::getConnection()?->hGetAll(sprintf('Client<%s>', $client->getUuid())));
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning('net.nosial.federationlib', sprintf('Failed to get Client from redis: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->update(DatabaseTables::CLIENTS);
|
||||
$qb->set('updated_timestamp', ':updated_timestamp');
|
||||
$qb->setParameter('updated_timestamp', time());
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $client->getUuid());
|
||||
|
||||
if($cached_client instanceof Client)
|
||||
{
|
||||
$data = array_diff($client->toArray(), $cached_client->toArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
$data = $client->toArray();
|
||||
}
|
||||
|
||||
foreach($data as $key => $value)
|
||||
{
|
||||
switch($key)
|
||||
{
|
||||
case 'uuid':
|
||||
case 'created_timestamp':
|
||||
case 'updated_timestamp':
|
||||
case 'seen_timestamp':
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
if($value === null || strlen($value) === 0 || !preg_match('/^[a-zA-Z0-9_\-]+$/', $value ))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
$qb->set($key, ':' . $key);
|
||||
$qb->setParameter($key, substr($value, 0, 64));
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
if($value !== null)
|
||||
{
|
||||
$qb->set($key, ':' . $key);
|
||||
$qb->setParameter($key, substr($value, 0, 255));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'enabled':
|
||||
$qb->set($key, ':' . $key);
|
||||
$qb->setParameter($key, $value ? 1 : 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
$qb->set($key, ':' . $key);
|
||||
$qb->setParameter($key, $value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to update client: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
if(Configuration::isRedisCacheClientObjectsEnabled())
|
||||
{
|
||||
// Update the differences in the cache
|
||||
if($cached_client instanceof Client)
|
||||
{
|
||||
try
|
||||
{
|
||||
Redis::getConnection()?->hMSet((string)$client, array_diff($client->toArray(), $cached_client->toArray()));
|
||||
Redis::getConnection()?->expire((string)$client, Configuration::getRedisCacheClientObjectsTTL());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning('net.nosial.federationlib', sprintf('Failed to cache client in redis: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Redis::getConnection()?->hMSet((string)$client, $client->toArray());
|
||||
Redis::getConnection()?->expire((string)$client, $client->getUuid(), Configuration::getRedisCacheClientObjectsTTL());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning('net.nosial.federationlib', sprintf('Failed to cache Client in redis: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a client's last seen timestamp.
|
||||
*
|
||||
* @param string|Client $uuid
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function updateLastSeen(string|Client $uuid): void
|
||||
{
|
||||
if($uuid instanceof Client)
|
||||
{
|
||||
$uuid = $uuid->getUuid();
|
||||
}
|
||||
|
||||
$timestamp = time();
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->update(DatabaseTables::CLIENTS);
|
||||
$qb->set('seen_timestamp', ':seen_timestamp');
|
||||
$qb->setParameter('seen_timestamp', $timestamp);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $uuid);
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to update last seen timestamp: ' . $e->getMessage(), $e);
|
||||
}
|
||||
|
||||
// Update the 'seen_timestamp' only in the hash table in redis
|
||||
if(Configuration::isRedisCacheClientObjectsEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
Redis::getConnection()?->hSet(sprintf('Client<%s>', $uuid), 'seen_timestamp', $timestamp);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning('net.nosial.federationlib', sprintf('Failed to update last seen timestamp in redis: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of clients based on the filter, order, and page.
|
||||
*
|
||||
* @param int $page
|
||||
* @param string $filter
|
||||
* @param string $order
|
||||
* @param int $max_items
|
||||
* @return array
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function listClients(int $page, string $filter=ListClientsFilter::CREATED_TIMESTAMP, string $order=FilterOrder::ASCENDING, int $max_items=100): array
|
||||
{
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select(
|
||||
'uuid', 'enabled', 'name', 'description', 'secret_totp', 'query_permission', 'update_permission',
|
||||
'flags', 'created_timestamp', 'updated_timestamp', 'seen_timestamp'
|
||||
);
|
||||
$qb->from(DatabaseTables::CLIENTS);
|
||||
$qb->setFirstResult(($page - 1) * $max_items);
|
||||
$qb->setMaxResults($max_items);
|
||||
|
||||
if($order !== FilterOrder::ASCENDING && $order !== FilterOrder::DESCENDING)
|
||||
{
|
||||
throw new DatabaseException('Invalid order: ' . $order);
|
||||
}
|
||||
|
||||
switch($filter)
|
||||
{
|
||||
case ListClientsFilter::CREATED_TIMESTAMP:
|
||||
$qb->orderBy('created_timestamp', strtoupper($order));
|
||||
break;
|
||||
|
||||
case ListClientsFilter::UPDATED_TIMESTAMP:
|
||||
$qb->orderBy('updated_timestamp', strtoupper($order));
|
||||
break;
|
||||
|
||||
case ListClientsFilter::SEEN_TIMESTAMP:
|
||||
$qb->orderBy('seen_timestamp', strtoupper($order));
|
||||
break;
|
||||
|
||||
case ListClientsFilter::ENABLED:
|
||||
$qb->orderBy('enabled', strtoupper($order));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new DatabaseException('Invalid filter: ' . $filter);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
$clients = [];
|
||||
|
||||
while($row = $result->fetchAssociative())
|
||||
{
|
||||
$clients[] = Client::fromArray($row);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to list clients: ' . $e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
unset($client);
|
||||
return $clients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of clients.
|
||||
*
|
||||
* @return int
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function getTotalClients(): int
|
||||
{
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->select('COUNT(uuid)');
|
||||
$qb->from(DatabaseTables::CLIENTS);
|
||||
|
||||
try
|
||||
{
|
||||
$result = $qb->executeQuery();
|
||||
$row = $result->fetchAssociative();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to get total clients: ' . $e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return (int)$row['COUNT(uuid)'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns teh total number of pages.
|
||||
*
|
||||
* @param int $max_items
|
||||
* @return int
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function getTotalPages(int $max_items=100): int
|
||||
{
|
||||
return (int)ceil($this->getTotalClients() / $max_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing client from the database.
|
||||
*
|
||||
* @param string|Client $uuid
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function deleteClient(string|Client $uuid): void
|
||||
{
|
||||
if($uuid instanceof Client)
|
||||
{
|
||||
$uuid = $uuid->getUuid();
|
||||
}
|
||||
|
||||
$qb = Database::getConnection()->createQueryBuilder();
|
||||
$qb->delete(DatabaseTables::CLIENTS);
|
||||
$qb->where('uuid = :uuid');
|
||||
$qb->setParameter('uuid', $uuid);
|
||||
|
||||
try
|
||||
{
|
||||
$qb->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException('Failed to delete client: ' . $e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
// Invalidate the cache
|
||||
if(Configuration::isRedisCacheClientObjectsEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
Redis::getConnection()?->del(sprintf('Client<%s>', $uuid));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
Log::warning('net.nosial.federationlib', sprintf('Failed to invalidate client cache in redis: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
172
src/FederationLib/Managers/PeerManager.php
Normal file
172
src/FederationLib/Managers/PeerManager.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Managers;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Exception;
|
||||
use FederationLib\Classes\Configuration;
|
||||
use FederationLib\Classes\Database;
|
||||
use FederationLib\Enums\DatabaseTables;
|
||||
use FederationLib\Enums\Misc;
|
||||
use FederationLib\Enums\Standard\InternetPeerType;
|
||||
use FederationLib\Enums\Standard\PeerType;
|
||||
use FederationLib\Enums\Standard\UserPeerType;
|
||||
use FederationLib\Exceptions\DatabaseException;
|
||||
use FederationLib\Exceptions\Standard\PeerNotFoundException;
|
||||
use FederationLib\Exceptions\Standard\UnsupportedPeerType;
|
||||
use FederationLib\FederationLib;
|
||||
use FederationLib\Objects\Client;
|
||||
use FederationLib\Objects\ParsedFederatedAddress;
|
||||
use FederationLib\Objects\Peer;
|
||||
use LogLib\Log;
|
||||
|
||||
class PeerManager
|
||||
{
|
||||
/**
|
||||
* @var FederationLib
|
||||
*/
|
||||
private $federationLib;
|
||||
|
||||
/**
|
||||
* @param FederationLib $federationLib
|
||||
*/
|
||||
public function __construct(FederationLib $federationLib)
|
||||
{
|
||||
$this->federationLib = $federationLib;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Peer Type of the federated address, returns "unknown" if the
|
||||
* type is not supported by the server
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
private function getPeerType(string $type): string
|
||||
{
|
||||
if(in_array(strtolower($type), InternetPeerType::ALL))
|
||||
return PeerType::INTERNET;
|
||||
|
||||
if(in_array(strtolower($type), UserPeerType::ALL))
|
||||
return PeerType::USER;
|
||||
|
||||
return PeerType::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a raw federated address and returns a ParsedFederatedAddress object
|
||||
*
|
||||
* @param string $federated_address
|
||||
* @return ParsedFederatedAddress
|
||||
* @throws UnsupportedPeerType
|
||||
*/
|
||||
private function parseAddress(string $federated_address): ParsedFederatedAddress
|
||||
{
|
||||
$parsed_address = new ParsedFederatedAddress($federated_address);
|
||||
|
||||
if($this->getPeerType($parsed_address->getPeerType()) === PeerType::UNKNOWN)
|
||||
{
|
||||
throw new UnsupportedPeerType($parsed_address->getPeerType());
|
||||
}
|
||||
|
||||
return $parsed_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new peer into the database
|
||||
*
|
||||
* @param string|Client $client_uuid
|
||||
* @param string $federated_address
|
||||
* @return void
|
||||
* @throws DatabaseException
|
||||
* @throws UnsupportedPeerType
|
||||
*/
|
||||
public function registerPeer(string|Client $client_uuid, string $federated_address): void
|
||||
{
|
||||
// If the client_uuid is a Client object, get the UUID from it
|
||||
if ($client_uuid instanceof Client)
|
||||
{
|
||||
$client_uuid = $client_uuid->getUuid();
|
||||
}
|
||||
|
||||
// Check if the peer type is supported by the server
|
||||
$parsed_address = $this->parseAddress($federated_address);
|
||||
|
||||
try
|
||||
{
|
||||
// Generate a query to insert the peer into the database
|
||||
$query_builder = Database::getConnection()->createQueryBuilder();
|
||||
$query_builder->insert(DatabaseTables::PEERS);
|
||||
$timestamp = time();
|
||||
|
||||
$query_builder->values([
|
||||
'federated_address' => $query_builder->createNamedParameter($parsed_address->getAddress()),
|
||||
'client_first_seen' => $query_builder->createNamedParameter($client_uuid),
|
||||
'client_last_seen' => $query_builder->createNamedParameter($client_uuid),
|
||||
'active_restriction' => $query_builder->createNamedParameter(null, ParameterType::NULL),
|
||||
'discovered_timestamp' => $query_builder->createNamedParameter($timestamp, ParameterType::INTEGER),
|
||||
'seen_timestamp' => $query_builder->createNamedParameter($timestamp, ParameterType::INTEGER),
|
||||
]);
|
||||
|
||||
$query_builder->executeStatement();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException(sprintf('Failed to register peer %s: %s', $parsed_address->getAddress(), $e->getMessage()), $e);
|
||||
}
|
||||
|
||||
Log::info(Misc::FEDERATIONLIB, sprintf('Registered new peer: %s', $parsed_address->getAddress()));
|
||||
}
|
||||
|
||||
public function cachePeerObject(Peer $peer): void
|
||||
{
|
||||
if(!Configuration::isRedisEnabled() && !Configuration::isPeerObjectsCached())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a peer from the database by its federated address
|
||||
*
|
||||
* @param string $federated_address
|
||||
* @return Peer
|
||||
* @throws DatabaseException
|
||||
* @throws PeerNotFoundException
|
||||
* @throws UnsupportedPeerType
|
||||
*/
|
||||
public function getPeer(string $federated_address): Peer
|
||||
{
|
||||
// Check if the peer type is supported by the server
|
||||
$parsed_address = $this->parseAddress($federated_address);
|
||||
|
||||
try
|
||||
{
|
||||
$query_builder = Database::getConnection()->createQueryBuilder();
|
||||
$query_builder->select('*');
|
||||
$query_builder->from(DatabaseTables::PEERS);
|
||||
$query_builder->where('federated_address = :federated_address');
|
||||
$query_builder->setParameter('federated_address', $parsed_address->getAddress());
|
||||
$query_builder->setMaxResults(1);
|
||||
|
||||
$result = $query_builder->executeQuery();
|
||||
|
||||
if($result->rowCount() === 0)
|
||||
{
|
||||
throw new PeerNotFoundException($parsed_address->getAddress());
|
||||
}
|
||||
|
||||
return Peer::fromArray($result->fetchAssociative());
|
||||
}
|
||||
catch(PeerNotFoundException $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new DatabaseException(sprintf('Failed to get peer %s: %s', $parsed_address->getAddress(), $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
}
|
380
src/FederationLib/Objects/Client.php
Executable file
380
src/FederationLib/Objects/Client.php
Executable file
|
@ -0,0 +1,380 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Objects;
|
||||
|
||||
use FederationCLI\Utilities;
|
||||
use FederationLib\Classes\Security;
|
||||
use FederationLib\Interfaces\SerializableObjectInterface;
|
||||
|
||||
class Client implements SerializableObjectInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $uuid;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $enabled;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $secret_totp;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $query_permission;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $update_permission;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $flags;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $created_timestamp;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $updated_timestamp;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $seen_timestamp;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->enabled = true;
|
||||
$this->flags = [];
|
||||
$this->query_permission = 1;
|
||||
$this->update_permission = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client's unique ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $enabled
|
||||
*/
|
||||
public function setEnabled(bool $enabled): void
|
||||
{
|
||||
$this->enabled = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
*/
|
||||
public function setName(?string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $description
|
||||
*/
|
||||
public function setDescription(?string $description): void
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables authentication for the client, returns the secret.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function enableAuthentication(): string
|
||||
{
|
||||
$this->secret_totp = Security::generateSecret();
|
||||
return $this->secret_totp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables authentication for the client, wipes the secret.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disableAuthentication(): void
|
||||
{
|
||||
$this->secret_totp = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given authentication code against the client's secret.
|
||||
*
|
||||
* @param string|null $authentication_code
|
||||
* @param int|null $timestamp
|
||||
* @return bool
|
||||
*/
|
||||
public function validateAuthentication(?string $authentication_code=null, ?int $timestamp=null): bool
|
||||
{
|
||||
if($this->secret_totp === null)
|
||||
{
|
||||
// Always authenticate if authentication is disabled.
|
||||
return true;
|
||||
}
|
||||
|
||||
if($authentication_code === null)
|
||||
{
|
||||
// Authentication code not provided but required.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Authentication Code: sha1(client_id + totp_code)
|
||||
return hash('sha1', $this->uuid . Security::generateCode($this->secret_totp, $timestamp)) === $authentication_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client's query permission level.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getQueryPermission(): int
|
||||
{
|
||||
return $this->query_permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client's query permission level.
|
||||
*
|
||||
* @param int $query_permission
|
||||
*/
|
||||
public function setQueryPermission(int $query_permission): void
|
||||
{
|
||||
$this->query_permission = $query_permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client's update permission level.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getUpdatePermission(): int
|
||||
{
|
||||
return $this->update_permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client's update permission.
|
||||
*
|
||||
* @param int $update_permission
|
||||
*/
|
||||
public function setUpdatePermission(int $update_permission): void
|
||||
{
|
||||
$this->update_permission = $update_permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client's flags.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFlags(): array
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an array of flags for the client.
|
||||
* This function overrides any existing flags.
|
||||
*
|
||||
* @param string[] $flags
|
||||
*/
|
||||
public function setFlags(array $flags): void
|
||||
{
|
||||
$this->flags = $flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a flag to the client's flags.
|
||||
*
|
||||
* @param string $flag
|
||||
* @return void
|
||||
*/
|
||||
public function appendFlag(string $flag): void
|
||||
{
|
||||
if(!in_array($flag, $this->flags))
|
||||
{
|
||||
$this->flags[] = $flag;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a flag from the client's flags.
|
||||
*
|
||||
* @param string $flag
|
||||
* @return void
|
||||
*/
|
||||
public function removeFlag(string $flag): void
|
||||
{
|
||||
$this->flags = array_diff($this->flags, [$flag]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns True if the client has the given flag.
|
||||
*
|
||||
* @param string $flag
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFlag(string $flag): bool
|
||||
{
|
||||
return in_array($flag, $this->flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Unix Timestamp for when the client was last created on the server
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCreatedTimestamp(): int
|
||||
{
|
||||
return $this->created_timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Unix Timestamp for when the client was last updated on the server.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getUpdatedTimestamp(): int
|
||||
{
|
||||
return $this->updated_timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Unix Timestamp for when the client was last seen by the server.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSeenTimestamp(): int
|
||||
{
|
||||
return $this->seen_timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array representation of the object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$flags = null;
|
||||
|
||||
if($this->flags !== null && count($this->flags) > 0)
|
||||
{
|
||||
$flags = implode(',', $this->flags);
|
||||
}
|
||||
|
||||
return [
|
||||
'uuid' => $this->uuid,
|
||||
'enabled' => $this->enabled,
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'secret_totp' => $this->secret_totp,
|
||||
'query_permission' => $this->query_permission,
|
||||
'update_permission' => $this->update_permission,
|
||||
'flags' => $flags,
|
||||
'created_timestamp' => $this->created_timestamp,
|
||||
'updated_timestamp' => $this->updated_timestamp,
|
||||
'seen_timestamp' => $this->seen_timestamp,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs object from an array representation.
|
||||
*
|
||||
* @param array $array
|
||||
* @return Client
|
||||
*/
|
||||
public static function fromArray(array $array): Client
|
||||
{
|
||||
$client = new self();
|
||||
|
||||
$client->uuid = $array['uuid'] ?? null;
|
||||
$client->enabled = Utilities::parseBoolean($array['enabled']);
|
||||
$client->name = $array['name'] ?? null;
|
||||
$client->description = $array['description'] ?? null;
|
||||
$client->secret_totp = $array['secret_totp'] ?? null;
|
||||
$client->query_permission = $array['query_permission'] ?? 0;
|
||||
$client->update_permission = $array['update_permission'] ?? 0;
|
||||
if(isset($array['flags']))
|
||||
{
|
||||
$client->flags = explode(',', $array['flags']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$client->flags = [];
|
||||
}
|
||||
$client->created_timestamp = $array['created_timestamp'] ?? 0;
|
||||
$client->updated_timestamp = $array['updated_timestamp'] ?? 0;
|
||||
$client->seen_timestamp = $array['seen_timestamp'] ?? 0;
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object identifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf('Client<%s>', $this->uuid ?? spl_object_hash($this));
|
||||
}
|
||||
}
|
78
src/FederationLib/Objects/ParsedFederatedAddress.php
Normal file
78
src/FederationLib/Objects/ParsedFederatedAddress.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Objects;
|
||||
|
||||
class ParsedFederatedAddress
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $peer_type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $unique_identifier;
|
||||
|
||||
/**
|
||||
* Public Constructor, parses the federated address and sets the properties
|
||||
*
|
||||
* @param string $federated_address
|
||||
*/
|
||||
public function __construct(string $federated_address)
|
||||
{
|
||||
preg_match("/(?<source>[a-z0-9]+)\.(?<type>[a-z0-9]+):(?<id>.+)/", $federated_address, $matches);
|
||||
|
||||
$this->source = $matches['source'];
|
||||
$this->peer_type = $matches['type'];
|
||||
$this->unique_identifier = $matches['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the platform source of the peer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSource(): string
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the peer type of the platform
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPeerType(): string
|
||||
{
|
||||
return $this->peer_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Unique Identifier of the peer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueIdentifier(): string
|
||||
{
|
||||
return $this->unique_identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Standard Federated Address of the peer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAddress(): string
|
||||
{
|
||||
return sprintf('%s.%s:%s', $this->source, $this->peer_type, $this->unique_identifier);
|
||||
}
|
||||
|
||||
}
|
138
src/FederationLib/Objects/Peer.php
Normal file
138
src/FederationLib/Objects/Peer.php
Normal file
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpMissingFieldTypeInspection */
|
||||
|
||||
namespace FederationLib\Objects;
|
||||
|
||||
use FederationLib\Interfaces\SerializableObjectInterface;
|
||||
|
||||
class Peer implements SerializableObjectInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $federated_address;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $client_first_seen;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $client_last_seen;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $active_restriction;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $discovered_timestamp;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $seen_timestamp;
|
||||
|
||||
/**
|
||||
* Returns the Standard Federated Address for the peer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFederatedAddress(): string
|
||||
{
|
||||
return $this->federated_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client UUID that first saw the peer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientFirstSeen(): string
|
||||
{
|
||||
return $this->client_first_seen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client UUID that last saw the peer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientLastSeen(): string
|
||||
{
|
||||
return $this->client_last_seen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional. Returns the active restriction ID for the peer
|
||||
* This is only available if the peer is currently restricted
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getActiveRestriction(): ?string
|
||||
{
|
||||
return $this->active_restriction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Unix Timestamp for when the peer was first discovered by a client
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDiscoveredTimestamp(): int
|
||||
{
|
||||
return $this->discovered_timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Unix Timestamp for when the peer was last seen by a client
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSeenTimestamp(): int
|
||||
{
|
||||
return $this->seen_timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array representation of the object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'federated_address' => $this->federated_address,
|
||||
'client_first_seen' => $this->client_first_seen,
|
||||
'client_last_seen' => $this->client_last_seen,
|
||||
'active_restriction' => $this->active_restriction,
|
||||
'discovered_timestamp' => $this->discovered_timestamp,
|
||||
'seen_timestamp' => $this->seen_timestamp,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs object from an array representation
|
||||
*
|
||||
* @param array $array
|
||||
* @return Peer
|
||||
*/
|
||||
public static function fromArray(array $array): Peer
|
||||
{
|
||||
$object = new self();
|
||||
|
||||
$object->federated_address = $array['federated_address'] ?? null;
|
||||
$object->client_first_seen = $array['client_first_seen'] ?? null;
|
||||
$object->client_last_seen = $array['client_last_seen'] ?? null;
|
||||
$object->active_restriction = $array['active_restriction'] ?? null;
|
||||
$object->discovered_timestamp = $array['discovered_timestamp'] ?? null;
|
||||
$object->seen_timestamp = $array['seen_timestamp'] ?? null;
|
||||
|
||||
return $object;
|
||||
}
|
||||
}
|
17
src/FederationLib/Objects/QueryDocument.php
Normal file
17
src/FederationLib/Objects/QueryDocument.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace FederationLib\Objects;
|
||||
|
||||
class QueryDocument
|
||||
{
|
||||
private $subject_type;
|
||||
private $client_id;
|
||||
private $client_totp_signature;
|
||||
private $timstamp;
|
||||
|
||||
private $platform;
|
||||
private $event_type;
|
||||
private $channel_peer;
|
||||
private $resent_from_peer;
|
||||
private
|
||||
}
|
14
src/README.md
Normal file → Executable file
14
src/README.md
Normal file → Executable file
|
@ -1,3 +1,15 @@
|
|||
# FederationLib
|
||||
|
||||
Coming Soon ...
|
||||
Spam is a persistent problem on the internet, affecting various communication channels such as email, social media,
|
||||
messaging apps, and more. Spammers use different techniques to distribute spam, such as creating fake accounts, using
|
||||
automated bots or exploiting vulnerabilities in the system, this extends towards bad actors who may use these techniques
|
||||
to harm children, spread misinformation, or even commit financial fraud. In order to combat these issues, while different
|
||||
systems have developed methods for identifying and classifying spam, there is no standard or centralized way to share
|
||||
this information. This results in duplication of effort and inconsistencies in spam classification across different
|
||||
systems.
|
||||
|
||||
The objective of this project is to develop a decentralized, open-source, and privacy-preserving spam detection and
|
||||
classification system. This system will act as a decentralized authority for internet spam classification. The server
|
||||
will allow different organizations and individuals to contribute to the common database, to which the public may query
|
||||
the common database to check if the given content or entity could be classified as spam and provide a confidence score
|
||||
alongside with evidence to support the classification.
|
Loading…
Add table
Add a link
Reference in a new issue