diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index ea8af9c..a854623 100755
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -19,6 +19,9 @@
+
+
+
diff --git a/.idea/php.xml b/.idea/php.xml
index 432a2aa..fea1810 100644
--- a/.idea/php.xml
+++ b/.idea/php.xml
@@ -24,6 +24,7 @@
+
@@ -73,12 +74,10 @@
-
-
diff --git a/STANDARD.md b/STANDARD.md
index 6dfa733..0478669 100755
--- a/STANDARD.md
+++ b/STANDARD.md
@@ -18,6 +18,12 @@ spam and malicious content on various communication channels such as email, soci
* [Client TOTP Signature](#client-totp-signature)
* [Client Object](#client-object)
* [Federation Standard](#federation-standard)
+ * [Available Types](#available-types)
+ * [ClientIdentity Object](#clientidentity-object)
+ * [Invokable Methods](#invokable-methods)
+ * [ping Method](#ping-method)
+ * [whoami Method](#whoami-method)
+ * [create_client Method](#createclient-method)
* [Standard Federated Addresses](#standard-federated-addresses)
* [Query Document](#query-document)
* [QueryDocument Versioning](#querydocument-versioning)
@@ -73,6 +79,125 @@ TODO: Write this section
The federation standard is a standard which defines how clients should communicate with servers, the standard
+## Available Types
+
+
+### ClientIdentity Object
+
+The ClientIdentity object is used to identify the client, this standard object contains information about what client
+is invoking the method, this object is required to be provided by all clients when invoking a method, this object
+is used by the server to determine if the client is authorized to invoke the method and to determine what permissions
+the client has.
+
+In such cases where a peer is attempting to invoke a method through a client such as, the server will identify the
+invoker as the peer on behalf of the client, this is done by providing the peer's standard federated address in the
+`peer` field of the ClientIdentity object
+
+| Parameter | Type | Required | Description |
+|-------------------------|----------|----------|--------------------------------------------------------------------------------------------------------------------------------------|
+| `client_uuid` | `string` | Yes | The UUID of the client that is invoking the request |
+| `client_totp_signature` | `string` | No | Optional. The client's TOTP signature that proves the request came from the client, only applicable if the client uses authorization |
+| `peer` | `string` | No | Optional. The peer that's invoking the command on behalf of the client |
+
+ > Note: If a server has strict permissions enabled, in such cases where the peer has higher permissions than the
+ client, the server will default to maximum permissions as the client rather than the peer, this is to
+ prevent clients without high permissions from abusing the system by using a peer with higher permissions.
+
+------------------------------------------------------------------------------------------------------------------------
+
+## Invokable Methods
+
+Invokable Methods are used by clients, peers or the server to invoke a method on the server, the server will use the
+provided information to determine who's invoking the method and if the method is allowed to be invoked by the peer.
+
+All these methods are standard and required to be implemented by all servers, however, approach would allow for servers
+to implement additional methods to allow for additional functionality that may not be available in the standard,
+however, this would require the client to be aware of the additional methods and the server to be aware of the
+additional methods.
+
+These methods are supposed to invokable by a virtual shell or API endpoint, however, this means while the client does
+not need to implement or use the shell to invoke these methods but can use the API to achieve the same result as the
+shell.
+
+
+### ping Method
+
+The ping method is used to check if the server is online and to check the server's version, the ping method is
+available to all peers. Returns True if the execution was successful, otherwise the client should assume the server
+is offline or the server is temporarily unreachable.
+
+JSON-RPC Example:
+
+```jsonl
+ > {"jsonrpc": "2.0", "method": "ping", "id": 1}
+ < {"jsonrpc": "2.0", "result": true, "id": 1}
+```
+
+Shell Example:
+
+```shell
+$ ping
+true
+```
+
+Default Permission Table:
+
+| root | admin | operator | agent | client | guest |
+|:----:|:-----:|:--------:|:-----:|:------:|:-----:|
+| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+
+
+------------------------------------------------------------------------------------------------------------------------
+
+
+### whoami Method
+
+The whoami method is used to check the client's permissions and other information, the whoami method is available
+to all peers. Returns the identified "*uid*" of whoever is invoking the method, this can either be `root` if the
+function is being invoked by the server or from the root shell, or the client's UID if the function is being invoked
+by a client or finally the peer's Standard Federated Address if the function is being invoked by a peer.
+
+JSON-RPC Example:
+
+```jsonl
+ > {"jsonrpc": "2.0", "method": "whoami", "id": 1}
+ < {"jsonrpc": "2.0", "result": "root", "id": 1}
+```
+
+Shell Example:
+
+```shell
+$ whoami
+root
+```
+
+Default Permission Table:
+
+| root | admin | operator | agent | client | guest |
+|:----:|:-----:|:--------:|:-----:|:------:|:-----:|
+| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+
+
+------------------------------------------------------------------------------------------------------------------------
+
+
+### create_client Method
+
+The create_client method is used to create a new client, the create_client method is only available to the root & admin
+user by default, however, this can be changed by the server's configuration. Returns the UID of the newly created client
+if the execution was successful
+
+Parameters:
+
+| Name | Type | Required | Description |
+|------------------|----------|----------|:-------------------------------------------|
+| `uid` | `string` | Yes | The user ID of that is invoking the method |
+| `totp_signature` | `string` | Yes | The TOTP signature for authentication |
+| `name` | `string` | Yes | The username of the client |
+
+
+------------------------------------------------------------------------------------------------------------------------
+
## Standard Federated Addresses
A standard federated address is a universally unique identifier which can represent multiple types of peers from
diff --git a/project.json b/project.json
index cbc7e37..3f157b5 100755
--- a/project.json
+++ b/project.json
@@ -43,6 +43,18 @@
"source_type": "remote",
"source": "nosial/libs.config=latest@n64"
},
+ {
+ "name": "net.nosial.optslib",
+ "version": "latest",
+ "source_type": "remote",
+ "source": "nosial/libs.opts=latest@n64"
+ },
+ {
+ "name": "net.nosial.tamerlib",
+ "version": "latest",
+ "source_type": "remote",
+ "source": "nosial/libs.tamer=latest@n64"
+ },
{
"name": "net.nosial.loglib",
"version": "latest",
diff --git a/src/FederationCLI/InteractiveMode.php b/src/FederationCLI/InteractiveMode.php
index 710ac95..470e031 100644
--- a/src/FederationCLI/InteractiveMode.php
+++ b/src/FederationCLI/InteractiveMode.php
@@ -4,146 +4,220 @@
namespace FederationCLI;
+ use Exception;
+ use FederationLib\Classes\Configuration;
+ use FederationLib\Enums\Standard\Methods;
+ use FederationLib\Exceptions\DatabaseException;
+ use FederationLib\Exceptions\Standard\AccessDeniedException;
+ use FederationLib\Exceptions\Standard\ClientNotFoundException;
+ use FederationLib\Exceptions\Standard\InternalServerException;
+ use FederationLib\Exceptions\Standard\InvalidClientDescriptionException;
+ use FederationLib\Exceptions\Standard\InvalidClientNameException;
use FederationLib\FederationLib;
+ use JsonException;
+ use OptsLib\Parse;
+ use TamerLib\Enums\TamerMode;
+ use TamerLib\Exceptions\ServerException;
+ use TamerLib\Exceptions\WorkerFailedException;
+ use TamerLib\tm;
class InteractiveMode
{
/**
- * The current menu the user is in
- *
- * @var string
+ * @var FederationLib
*/
- 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;
+ private static $federation_lib;
/**
* Main entry point for the interactive mode
*
* @param array $args
* @return void
+ * @throws ServerException
+ * @throws WorkerFailedException
*/
public static function main(array $args=[]): void
{
+ tm::initialize(TamerMode::CLIENT, Configuration::getTamerLibConfiguration()->getServerConfiguration());
+ tm::createWorker(Configuration::getTamerLibConfiguration()->getCliWorkers(), FederationLib::getSubprocessorPath());
+
+ self::$federation_lib = new FederationLib();
+
while(true)
{
- print(sprintf('federation@%s:~$ ', self::$current_menu));
+ print(sprintf('%s@%s:~$ ', 'root', Configuration::getHostName()));
$input = trim(fgets(STDIN));
- self::processCommand($input);
+ try
+ {
+ switch(strtolower(explode(' ', $input)[0]))
+ {
+ case Methods::PING:
+ self::ping();
+ break;
+ case Methods::WHOAMI:
+ self::whoami();
+ break;
+
+ case Methods::CREATE_CLIENT:
+ self::createClient($input);
+ break;
+ case Methods::GET_CLIENT:
+ self::getClient($input);
+ break;
+ case Methods::CHANGE_CLIENT_NAME:
+ self::changeClientName($input);
+ break;
+
+ default:
+ print(sprintf('Command \'%s\' not found' . PHP_EOL, $input));
+ break;
+ }
+ }
+ catch(Exception $e)
+ {
+ print(sprintf('Error: %s' . PHP_EOL, $e->getMessage()));
+ }
}
}
/**
- * Processes a command from the user
+ * Invokes the ping method
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws InternalServerException
+ */
+ private static function ping(): void
+ {
+ if(self::$federation_lib->ping(null))
+ {
+ print('OK' . PHP_EOL);
+ return;
+ }
+
+ print('ERROR' . PHP_EOL);
+ }
+
+ /**
+ * Invokes the whoami method and prints the result
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws InternalServerException
+ */
+ private static function whoami(): void
+ {
+ print(self::$federation_lib->whoami(null) . PHP_EOL);
+ }
+
+ /**
+ * @param string $input
+ * @return void
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws DatabaseException
+ * @throws InternalServerException
+ * @throws InvalidClientDescriptionException
+ * @throws InvalidClientNameException
+ */
+ private static function createClient(string $input): void
+ {
+ $parsed_arguments = Parse::parseArgument($input);
+
+ $name = $parsed_arguments['name'] ?? $parsed_arguments['n'] ?? null;
+ $description = $parsed_arguments['description'] ?? $parsed_arguments['d'] ?? null;
+
+ print(self::$federation_lib->createClient(null, $name, $description) . PHP_EOL);
+ }
+
+ /**
+ * @param string $input
+ * @return void
+ * @throws AccessDeniedException
+ * @throws DatabaseException
+ * @throws InternalServerException
+ * @throws JsonException
+ * @noinspection PhpMultipleClassDeclarationsInspection
+ */
+ private static function getClient(string $input): void
+ {
+ $parsed_arguments = Parse::parseArgument($input);
+
+ $uuid = $parsed_arguments['uuid'] ?? $parsed_arguments['u'] ?? null;
+ $raw = $parsed_arguments['raw'] ?? $parsed_arguments['r'] ?? false;
+
+ if($uuid === null | $uuid === '')
+ {
+ print('Missing required argument \'uuid\'' . PHP_EOL);
+ return;
+ }
+
+ try
+ {
+ $client = self::$federation_lib->getClient(null, $uuid);
+
+ if($raw)
+ {
+ print(json_encode($client->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) . PHP_EOL);
+ return;
+ }
+
+ Utilities::printData('CLIENT LOOKUP RESULTS', [
+ 'UUID' => $client->getUuid(),
+ 'NAME' => $client->getName(),
+ 'DESCRIPTION' => $client->getDescription() ?? 'N/A',
+ 'PERMISSION_ROLE' => $client->getPermissionRole(),
+ 'CREATED_AT' => date('Y-m-d H:i:s', $client->getCreatedTimestamp()),
+ 'UPDATED_AT' => date('Y-m-d H:i:s', $client->getUpdatedTimestamp()),
+ 'SEEN_AT' => date('Y-m-d H:i:s', $client->getSeenTimestamp())
+ ]);
+ }
+ catch(ClientNotFoundException)
+ {
+ print(sprintf('Client with UUID \'%s\' not found' . PHP_EOL, $uuid));
+ }
+ }
+
+ /**
+ * Changes the name of a client with the given UUID
*
* @param string $input
* @return void
+ * @throws AccessDeniedException
+ * @throws DatabaseException
+ * @throws InternalServerException
+ * @throws InvalidClientNameException
*/
- private static function processCommand(string $input): void
+ private static function changeClientName(string $input): void
{
- $parsed_input = Utilities::parseShellInput($input);
+ $parsed_arguments = Parse::parseArgument($input);
- switch(strtolower($parsed_input['command']))
+ $uuid = $parsed_arguments['uuid'] ?? $parsed_arguments['u'] ?? null;
+ $name = $parsed_arguments['name'] ?? $parsed_arguments['n'] ?? null;
+
+ if($uuid === null | $uuid === '')
{
- 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();
+ print('Missing required argument \'uuid\'' . PHP_EOL);
+ return;
}
- return self::$federation_lib;
+ if($name === null | $name === '')
+ {
+ print('Missing required argument \'name\'' . PHP_EOL);
+ return;
+ }
+
+ try
+ {
+ self::$federation_lib->changeClientName(null, $uuid, $name);
+ print('OK' . PHP_EOL);
+ }
+ catch(ClientNotFoundException)
+ {
+ print(sprintf('Client with UUID \'%s\' not found' . PHP_EOL, $uuid));
+ }
}
}
\ No newline at end of file
diff --git a/src/FederationCLI/InteractiveMode/ClientManager.php b/src/FederationCLI/InteractiveMode/ClientManager.php
deleted file mode 100644
index 84174d6..0000000
--- a/src/FederationCLI/InteractiveMode/ClientManager.php
+++ /dev/null
@@ -1,190 +0,0 @@
-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);
- }
- }
\ No newline at end of file
diff --git a/src/FederationCLI/InteractiveMode/ConfigurationManager.php b/src/FederationCLI/InteractiveMode/ConfigurationManager.php
deleted file mode 100644
index 7d5c0c2..0000000
--- a/src/FederationCLI/InteractiveMode/ConfigurationManager.php
+++ /dev/null
@@ -1,113 +0,0 @@
- - reads the value of the specified configuration key' . PHP_EOL);
- print(' write - 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);
- }
- }
- }
\ No newline at end of file
diff --git a/src/FederationCLI/Utilities.php b/src/FederationCLI/Utilities.php
index 325afeb..ae9c848 100644
--- a/src/FederationCLI/Utilities.php
+++ b/src/FederationCLI/Utilities.php
@@ -98,4 +98,45 @@
return self::parseBoolean($input);
}
+
+ /**
+ * Prints data in a formatted manner
+ *
+ * @param $banner_text
+ * @param $data
+ * @param int $body_width
+ * @return void
+ */
+ public static function printData($banner_text, $data, int $body_width = 70)
+ {
+ // Padding and wrap for the longest key
+ $max_key_len = max(array_map('strlen', array_keys($data)));
+
+ // Adjust padding for body_width
+ $padding = $body_width - ($max_key_len + 4); // Additional 2 spaces for initial padding
+
+ // Banner
+ $banner_width = $body_width + 2;
+ echo str_repeat("*", $banner_width) . PHP_EOL;
+ echo "* " . str_pad($banner_text, $banner_width - 4, ' ', STR_PAD_RIGHT) . " *" . PHP_EOL;
+ echo str_repeat("*", $banner_width) . PHP_EOL;
+
+ // Print data
+ foreach ($data as $key => $value) {
+ // Split value into lines if it's too long
+ $lines = str_split($value, $padding);
+
+ // Print lines
+ foreach ($lines as $i => $line) {
+ if ($i == 0) {
+ // First line - print key and value
+ echo ' ' . str_pad(strtoupper($key), $max_key_len, ' ', STR_PAD_RIGHT) . ' ' . $line . PHP_EOL;
+ } else {
+ // Additional lines - only value
+ echo str_repeat(' ', $max_key_len + 4) . $line . PHP_EOL;
+ }
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/FederationLib/Classes/Configuration.php b/src/FederationLib/Classes/Configuration.php
index 05315ff..87320df 100644
--- a/src/FederationLib/Classes/Configuration.php
+++ b/src/FederationLib/Classes/Configuration.php
@@ -6,6 +6,9 @@
use Exception;
use FederationLib\Classes\Configuration\CacheServerConfiguration;
+ use FederationLib\Classes\Configuration\TamerLibConfiguration;
+ use FederationLib\Enums\Standard\Methods;
+ use FederationLib\Enums\Standard\PermissionRole;
use RuntimeException;
class Configuration
@@ -15,6 +18,11 @@
*/
private static $configuration;
+ /**
+ * @var TamerLibConfiguration|null
+ */
+ private static $tamerlib_configuration;
+
/**
* Returns the full raw configuration array.
*
@@ -37,46 +45,55 @@
self::$configuration->setDefault('database.reconnect_interval', 1800);
/** Multi-Cache Configuration */
- self::$configuration->setDefault('cache_system.enabled', true);
// Cache System Configuration
+ self::$configuration->setDefault('cache_system.enabled', true);
+ self::$configuration->setDefault('cache_system.opened_connection_priority', 20); // Higher is better
+ self::$configuration->setDefault('cache_system.error_connection_priority', -30); // Lower is better
// 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');
+ self::$configuration->setDefault('cache_system.cache.client_objects_server_fallback', 'redis_slave');
// 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.cache.peer_objects_server_fallback', 'redis_slave');
+ /** Multi-Cache Server Configuration */
+ // Redis Master 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.priority', 100);
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);
-
-
+ // Redis Slave Configuration
+ self::$configuration->setDefault('cache_system.servers.redis_slave.enabled', false);
+ self::$configuration->setDefault('cache_system.servers.redis_slave.host', 'localhost');
+ self::$configuration->setDefault('cache_system.servers.redis_slave.port', 11211);
+ self::$configuration->setDefault('cache_system.servers.redis_slave.priority', 50);
+ self::$configuration->setDefault('cache_system.servers.redis_slave.password', null);
+ self::$configuration->setDefault('cache_system.servers.redis_slave.database', null);
+ self::$configuration->setDefault('cache_system.servers.redis_slave.reconnect_interval', 1800);
/** Federation Configuration */
+ self::$configuration->setDefault('federation.hostname', 'FederationLib'); // Server Hostname
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);
+ self::$configuration->setDefault('federation.security.strict_permissions', true); // Security Feature, prevents clients & peers from elevating their permissions
+ self::$configuration->setDefault('federation.security.method_permissions.ping', 5); // Guest or above
+ self::$configuration->setDefault('federation.security.method_permissions.whoami', 5); // Guest or above
+ self::$configuration->setDefault('federation.security.method_permissions.create_client', 2); // Admin or above
+ self::$configuration->setDefault('federation.security.method_permissions.get_client', 2); // Admin or above
+ self::$configuration->setDefault('federation.security.method_permissions.update_client_name', 2); // Admin or above
+ /** TamerLib Configuration */
+ self::$configuration->setDefault('federation.tamer_lib.cli_workers', 8);
+ self::$configuration->setDefault('federation.tamer_lib.node_workers', 20);
+ self::$configuration->setDefault('federation.tamer_lib.server.host', '127.0.0.1');
+ self::$configuration->setDefault('federation.tamer_lib.server.port', 6379);
+ self::$configuration->setDefault('federation.tamer_lib.server.password', null);
+ self::$configuration->setDefault('federation.tamer_lib.server.database', null);
/** Save the configuration's default values if they don't exist */
try
@@ -107,6 +124,19 @@
return self::$configuration;
}
+ /**
+ * @return TamerLibConfiguration
+ */
+ public static function getTamerLibConfiguration(): TamerLibConfiguration
+ {
+ if(self::$tamerlib_configuration === null)
+ {
+ self::$tamerlib_configuration = new TamerLibConfiguration(self::getConfiguration());
+ }
+
+ return self::$tamerlib_configuration;
+ }
+
/**
* Returns driver of the database.
*
@@ -194,6 +224,46 @@
return (int)self::getConfiguration()['database']['reconnect_interval'];
}
+ /**
+ * Returns the hostname of the server.
+ *
+ * @return string
+ */
+ public static function getHostName(): string
+ {
+ return self::getConfiguration()['federation']['hostname'];
+ }
+
+ /**
+ * Returns True if the strict permission system is enabled and False if not.
+ *
+ * @return bool
+ */
+ public static function strictPermissionEnabled(): bool
+ {
+ return (bool)self::getConfiguration()['federation']['security']['strict_permissions'];
+ }
+
+ /**
+ * Returns the permission level required to execute a command.
+ *
+ * @param string $command
+ * @return int
+ */
+ public static function getMethodPermission(string $command): int
+ {
+ if(isset(self::getConfiguration()['federation']['security']['method_permissions'][$command]))
+ {
+ return (int)self::getConfiguration()['federation']['security']['method_permissions'][$command];
+ }
+
+ return match ($command)
+ {
+ Methods::CREATE_CLIENT => PermissionRole::ADMIN,
+ default => PermissionRole::GUEST,
+ };
+ }
+
/**
* Returns True if the cache system is enabled for FederationLib
* and False if not, based on the configuration.
@@ -223,4 +293,24 @@
return $results;
}
+
+ /**
+ * Returns the additional priority to add/remove if the connection is opened.
+ *
+ * @return int
+ */
+ public static function getCacheOpenedConnectionPriority(): int
+ {
+ return (int)self::getConfiguration()['cache_system']['opened_connection_priority'];
+ }
+
+ /**
+ * Returns additional priority to add/remove if the connection is closed.
+ *
+ * @return int
+ */
+ public static function getCacheErrorConnectionPriority(): int
+ {
+ return (int)self::getConfiguration()['cache_system']['error_connection_priority'];
+ }
}
\ No newline at end of file
diff --git a/src/FederationLib/Classes/Configuration/CacheServerConfiguration.php b/src/FederationLib/Classes/Configuration/CacheServerConfiguration.php
index 3c880cd..3a51aa8 100644
--- a/src/FederationLib/Classes/Configuration/CacheServerConfiguration.php
+++ b/src/FederationLib/Classes/Configuration/CacheServerConfiguration.php
@@ -24,21 +24,11 @@
*/
private ?int $port;
- /**
- * @var string|null
- */
- private ?string $driver;
-
/**
* @var int|null
*/
private ?int $priority;
- /**
- * @var string|null
- */
- private ?string $username;
-
/**
* @var string|null
*/
@@ -57,6 +47,7 @@
/**
* CacheServerConfiguration constructor.
*
+ * @param string $name
* @param array $configuration
*/
public function __construct(string $name, array $configuration)
@@ -65,9 +56,7 @@
$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;
@@ -76,7 +65,7 @@
/**
* Returns the name of the cache server
*
- * @return string
+ * @return string|null
*/
public function getName(): ?string
{
@@ -88,7 +77,7 @@
*
* @return bool
*/
- public function getEnabled(): bool
+ public function isEnabled(): bool
{
return $this->enabled ?? false;
}
@@ -111,14 +100,6 @@
return $this->port;
}
- /**
- * @return string|null
- */
- public function getDriver(): ?string
- {
- return $this->driver;
- }
-
/**
* @return int|null
*/
@@ -127,14 +108,6 @@
return $this->priority;
}
- /**
- * @return string|null
- */
- public function getUsername(): ?string
- {
- return $this->username;
- }
-
/**
* @return string|null
*/
diff --git a/src/FederationLib/Classes/Configuration/TamerLibConfiguration.php b/src/FederationLib/Classes/Configuration/TamerLibConfiguration.php
new file mode 100644
index 0000000..8880075
--- /dev/null
+++ b/src/FederationLib/Classes/Configuration/TamerLibConfiguration.php
@@ -0,0 +1,71 @@
+cli_workers = $configuration['federation.tamer_lib.cli_workers'] ?? 8;
+ $this->node_workers = $configuration['federation.tamer_lib.node_workers'] ?? 20;
+
+ $this->server_configuration = new ServerConfiguration(
+ $configuration['federation.tamer_lib.server.host'] ?? '127.0.0.1',
+ $configuration['federation.tamer_lib.server.port'] ?? 6379,
+ $configuration['federation.tamer_lib.server.password'] ?? null,
+ $configuration['federation.tamer_lib.server.database'] ?? 0
+ );
+ }
+
+ /**
+ * Returns the total number of CLI workers to use.
+ *
+ * @return int|mixed
+ */
+ public function getCliWorkers(): mixed
+ {
+ return $this->cli_workers;
+ }
+
+ /**
+ * Returns the total number of Node workers to use.
+ *
+ * @return int|mixed
+ */
+ public function getNodeWorkers(): mixed
+ {
+ return $this->node_workers;
+ }
+
+ /**
+ * Returns the ServerConfiguration object for TamerLib.
+ *
+ * @return ServerConfiguration
+ */
+ public function getServerConfiguration(): ServerConfiguration
+ {
+ return $this->server_configuration;
+ }
+ }
\ No newline at end of file
diff --git a/src/FederationLib/Classes/Database.php b/src/FederationLib/Classes/Database.php
index 2d81f98..b1e19d4 100644
--- a/src/FederationLib/Classes/Database.php
+++ b/src/FederationLib/Classes/Database.php
@@ -36,7 +36,7 @@
{
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()));
+ Log::debug('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(),
@@ -63,7 +63,7 @@
/** @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()));
+ Log::debug('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();
diff --git a/src/FederationLib/Classes/Memcached.php b/src/FederationLib/Classes/Memcached.php
deleted file mode 100644
index 98bac1f..0000000
--- a/src/FederationLib/Classes/Memcached.php
+++ /dev/null
@@ -1,18 +0,0 @@
-configuration = $configuration;
+ $this->connection_error = false;
+ }
+
+ /**
+ * Indicates if the redis server is available to connect to
+ *
+ * @return bool
+ */
+ public function isAvailable(): bool
+ {
+ if(!$this->configuration->isEnabled())
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the redis server is connected.
+ *
+ * @return bool
+ */
+ public function isConnected(): bool
+ {
+ if(!$this->configuration->isEnabled())
+ {
+ return false;
+ }
+
+ return $this->redis_connection !== null;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isConnectionError(): bool
+ {
+ return $this->connection_error;
+ }
+
+ /**
+ * Disconnects from the redis server if it's connected.
+ *
+ * @param bool $reset_error
+ * @return void
+ */
+ public function disconnect(bool $reset_error=true): void
+ {
+ if(!$this->isConnected())
+ {
+ return;
+ }
+
+ try
+ {
+ $this->redis_connection->close();
+ }
+ catch(Exception $e)
+ {
+ Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to disconnect from redis server: %s', $e->getMessage()));
+ }
+
+ if($reset_error)
+ {
+ $this->connection_error = false;
+ }
+
+ $this->redis_connection = null;
+ }
+
+ /**
+ * Establishes a connection to the redis server.
+ *
+ * @param bool $throw_exception
+ * @return void
+ * @throws CacheConnectionException
+ * @throws CacheDriverException
+ */
+ public function connect(bool $throw_exception=true): void
+ {
+ if(!$this->configuration->isEnabled())
+ {
+ if($throw_exception)
+ {
+ throw new CacheDriverException(sprintf('Failed to connect to the redis server \'%s\' because it\'s disabled.', $this->configuration->getName()));
+ }
+ }
+
+ if($this->redis_connection !== null)
+ {
+ if($this->redis_last_connection_time === null)
+ {
+ return;
+ }
+
+ if($this->redis_last_connection_time < (time() - $this->configuration->getReconnectInterval()))
+ {
+ Log::verbose(Misc::FEDERATIONLIB, sprintf('Interval limit of %s seconds to reconnect to the redis server \'%s\' has been reached, reconnecting...', $this->configuration->getReconnectInterval(), $this->configuration->getName()));
+ $this->disconnect();
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ try
+ {
+ Log::info(Misc::FEDERATIONLIB, sprintf('Connecting to the redis server \'%s\': %s:%s', $this->configuration->getName(), $this->configuration->getHost(), $this->configuration->getPort()));
+ $redis = new \Redis();
+ $redis->connect($this->configuration->getHost(), $this->configuration->getPort());
+ if($this->configuration->getPassword() !== null)
+ {
+ $redis->auth($this->configuration->getPassword());
+ }
+ $redis->select($this->configuration->getDatabase());
+ }
+ catch(Exception $e)
+ {
+ $this->connection_error = true;
+
+ if($throw_exception)
+ {
+ throw new CacheConnectionException(sprintf('Failed to connect to the redis server \'%s\': %s', $this->configuration->getName(), $e->getMessage()), $e->getCode(), $e);
+ }
+
+ Log::warning(Misc::FEDERATIONLIB, sprintf('Failed to connect to the redis server \'%s\': %s', $this->configuration->getName(), $e->getMessage()));
+ return;
+ }
+
+ $this->redis_connection = $redis;
+ $this->redis_last_connection_time = time();
+ }
/**
* Returns/Establishes a connection to the redis server.
* Returns null if redis is disabled.
*
+ * @param bool $throw_exception
* @return \Redis|null
- * @throws RedisException
+ * @throws CacheConnectionException
+ * @throws CacheDriverException
*/
- public static function getConnection(): ?\Redis
+ public function getConnection(bool $throw_exception=true): ?\Redis
{
- if(!Configuration::isRedisEnabled())
+ if(!$this->configuration->isEnabled())
{
return null;
}
- if(self::$redis_connection === null)
+ if(!$this->isConnected())
{
- 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();
+ $this->connect($throw_exception);
}
else
{
- if(self::$redis_last_connection_time === null || self::$redis_last_connection_time < (time() - Configuration::getRedisReconnectInterval()))
+ if($this->redis_last_connection_time < (time() - $this->configuration->getReconnectInterval()))
{
- 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();
+ $this->connect($throw_exception);
}
}
- return self::$redis_connection;
+ return $this->redis_connection;
}
/**
@@ -84,8 +213,17 @@
*
* @return int|null
*/
- public static function getRedisLastConnectionTime(): ?int
+ public function getRedisLastConnectionTime(): ?int
{
- return self::$redis_last_connection_time;
+ return $this->redis_last_connection_time;
}
+
+ /**
+ * @return CacheServerConfiguration
+ */
+ public function getConfiguration(): CacheServerConfiguration
+ {
+ return $this->configuration;
+ }
+
}
\ No newline at end of file
diff --git a/src/FederationLib/Classes/Utilities.php b/src/FederationLib/Classes/Utilities.php
index 69ff3f5..d861a07 100644
--- a/src/FederationLib/Classes/Utilities.php
+++ b/src/FederationLib/Classes/Utilities.php
@@ -4,6 +4,7 @@
namespace FederationLib\Classes;
+ use Exception;
use FederationLib\Enums\SerializationMethod;
use FederationLib\Interfaces\SerializableObjectInterface;
use InvalidArgumentException;
@@ -180,4 +181,34 @@
}
return $outliers;
}
+
+ public static function weightedRandomPick( array $data): string
+ {
+ $totalWeight = array_sum($data);
+ if($totalWeight == 0)
+ {
+ throw new InvalidArgumentException('Total weight cannot be 0');
+ }
+
+ // Normalize weights to 0-1
+ foreach ($data as $item => $weight)
+ {
+ $data[$item] = $weight / $totalWeight;
+ }
+
+ // Generate a random number between 0 and 1
+ $rand = mt_rand() / getrandmax();
+
+ // Select an item
+ $cumulativeWeight = 0.0;
+ foreach ($data as $item => $weight)
+ {
+ $cumulativeWeight += $weight;
+
+ if ($rand < $cumulativeWeight)
+ {
+ return $item;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/FederationLib/Classes/Validate.php b/src/FederationLib/Classes/Validate.php
index 9c13184..2ed75df 100644
--- a/src/FederationLib/Classes/Validate.php
+++ b/src/FederationLib/Classes/Validate.php
@@ -5,6 +5,7 @@
use FederationLib\Enums\Standard\PeerType;
use FederationLib\Enums\Standard\InternetPeerType;
use FederationLib\Enums\Standard\PeerAssociationType;
+ use FederationLib\Enums\Standard\PermissionRole;
use FederationLib\Enums\Standard\UserPeerType;
class Validate
@@ -57,4 +58,48 @@
return false;
}
+
+ /**
+ * Validates a client name based on certain criteria.
+ *
+ * The client name must be alphanumeric, allowing spaces, periods, dashes, and underscores,
+ * with a minimum length of 3 characters and a maximum length of 42 characters.
+ *
+ * @param string $name The client name to validate
+ * @return bool Returns true if the client name is valid, false otherwise
+ */
+ public static function clientName(string $name): bool
+ {
+ if (!preg_match('/^[a-zA-Z0-9\s\.\-_]+$/', $name))
+ {
+ return false;
+ }
+
+ $length = strlen($name);
+ return !($length < 3 || $length > 42);
+ }
+
+ /**
+ * Validates a client description based on certain criteria.
+ *
+ * @param string $description The client description to validate
+ * @return bool Returns true if the client description is valid, false otherwise
+ */
+ public static function clientDescription(string $description): bool
+ {
+ $length = strlen($description);
+ return !($length < 3 || $length > 255);
+ }
+
+ /**
+ * Validates if the given permission role is valid.
+ *
+ * @param string|int $role
+ * @return bool
+ */
+ public static function permissionRole(string|int $role): bool
+ {
+ return (int)$role >= 0 && (int)$role <= 5;
+ }
+
}
\ No newline at end of file
diff --git a/src/FederationLib/Enums/CommandApplets.php b/src/FederationLib/Enums/CommandApplets.php
new file mode 100644
index 0000000..054e78d
--- /dev/null
+++ b/src/FederationLib/Enums/CommandApplets.php
@@ -0,0 +1,22 @@
+ 'root',
+ self::ADMIN => 'admin',
+ self::OPERATOR => 'operator',
+ self::AGENT => 'agent',
+ self::CLIENT => 'client',
+ self::GUEST => 'guest'
+ ];
+ }
\ No newline at end of file
diff --git a/src/FederationLib/Exceptions/CacheConnectionException.php b/src/FederationLib/Exceptions/CacheConnectionException.php
new file mode 100644
index 0000000..c4be109
--- /dev/null
+++ b/src/FederationLib/Exceptions/CacheConnectionException.php
@@ -0,0 +1,19 @@
+client_manager = new ClientManager($this);
- $this->event_log_manager = new EventLogManager($this);
}
/**
- * Returns the Client manager instance
+ * Registers functions to the TamerLib instance, if applicable
*
- * @return ClientManager
+ * @return void
*/
- public function getClientManager(): ClientManager
+ public function registerFunctions(): void
{
- return $this->client_manager;
+ if(tm::getMode() !== TamerMode::WORKER)
+ {
+ return;
+ }
+
+ $this->client_manager->registerFunctions();
}
/**
- * @return EventLogManager
+ * Resolves the permission role from the given identity and attempts to check if the identity has the
+ * required permission to perform the given method
+ *
+ * @param ClientIdentity|null $identity
+ * @return ResolvedIdentity
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws InternalServerException
*/
- public function getEventLogManager(): EventLogManager
+ private function resolveIdentity(?ClientIdentity $identity): ResolvedIdentity
{
- return $this->event_log_manager;
+ if($identity === null)
+ {
+ return new ResolvedIdentity(null, null, true);
+ }
+
+ $get_client = tm::do('client_getClient', [$identity->getClientUuid()]);
+ $peer = null;
+
+ try
+ {
+ $client = tm::waitFor($get_client);
+ }
+ catch(ClientNotFoundException $e)
+ {
+ tm::clear();
+ throw new ClientNotFoundException('The client you are trying to access does not exist', $e);
+ }
+ catch(Exception|Throwable $e)
+ {
+ tm::clear();
+ throw new InternalServerException('There was an error while trying to access the client', $e);
+ }
+
+ tm::dof('client_updateLastSeen');
+ return new ResolvedIdentity($client, $peer);
}
+
+ /**
+ * Checks if the given identity has the required permission to perform the given method
+ *
+ * @param string $method
+ * @param ResolvedIdentity $resolved_identity
+ * @return bool
+ */
+ private function checkPermission(string $method, ResolvedIdentity $resolved_identity): bool
+ {
+ return $resolved_identity->getPermissionRole() <= Configuration::getMethodPermission($method);
+ }
+
+ /**
+ * Pings the client
+ *
+ * @param ClientIdentity|null $identity
+ * @return bool
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws InternalServerException
+ */
+ public function ping(?ClientIdentity $identity): bool
+ {
+ if(!$this->checkPermission(Methods::PING, $this->resolveIdentity($identity)))
+ {
+ throw new Exceptions\Standard\AccessDeniedException('You do not have permission to perform this action');
+ }
+
+ return true;
+ }
+
+ /**
+ * @param ClientIdentity|null $identity
+ * @return string
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws InternalServerException
+ */
+ public function whoami(?ClientIdentity $identity): string
+ {
+ $resolved_identity = $this->resolveIdentity($identity);
+
+ if(!$this->checkPermission(Methods::WHOAMI, $resolved_identity))
+ {
+ throw new Exceptions\Standard\AccessDeniedException('You do not have permission to perform this action');
+ }
+
+ if($resolved_identity->getPeer() !== null)
+ {
+ return $resolved_identity->getPeer()->getFederatedAddress();
+ }
+
+ if($resolved_identity->getClient() !== null)
+ {
+ return $resolved_identity->getClient()->getUuid();
+ }
+
+ return 'root';
+ }
+
+ /**
+ * Registers a new client into the database
+ *
+ * @param ClientIdentity|null $identity
+ * @param string|null $name
+ * @param string|null $description
+ * @return string
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws DatabaseException
+ * @throws InternalServerException
+ * @throws InvalidClientDescriptionException
+ * @throws InvalidClientNameException
+ */
+ public function createClient(?ClientIdentity $identity, ?string $name=null, ?string $description=null): string
+ {
+ if(!$this->checkPermission(Methods::CREATE_CLIENT, $this->resolveIdentity($identity)))
+ {
+ throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to create a client');
+ }
+
+ try
+ {
+ return $this->client_manager->registerClient($name, $description);
+ }
+ catch(Exception $e)
+ {
+ if(in_array($e->getCode(), ErrorCodes::ALL, true))
+ {
+ throw $e;
+ }
+
+ throw new Exceptions\Standard\InternalServerException('There was an error while creating the client', $e);
+ }
+ }
+
+ /**
+ * Returns an existing client from the database
+ *
+ * @param ClientIdentity|null $identity
+ * @param string|Client $client_uuid
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws DatabaseException
+ * @throws InternalServerException
+ * @return Objects\Standard\Client
+ */
+ public function getClient(?ClientIdentity $identity, string|Client $client_uuid): Objects\Standard\Client
+ {
+ if(!$this->checkPermission(Methods::GET_CLIENT, $this->resolveIdentity($identity)))
+ {
+ throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to fetch a client from the database');
+ }
+
+ try
+ {
+ // Return the standard client object
+ return new Objects\Standard\Client($this->client_manager->getClient($client_uuid));
+ }
+ catch(Exception $e)
+ {
+ if(in_array($e->getCode(), ErrorCodes::ALL, true))
+ {
+ throw $e;
+ }
+
+ throw new Exceptions\Standard\InternalServerException('There was an error while getting the client', $e);
+ }
+ }
+
+ /**
+ * Updates the name of an existing client, return True if successful
+ *
+ * @param ClientIdentity|null $identity
+ * @param string $client_uuid
+ * @param string $new_name
+ * @return bool
+ * @throws AccessDeniedException
+ * @throws ClientNotFoundException
+ * @throws DatabaseException
+ * @throws InternalServerException
+ * @throws InvalidClientNameException
+ */
+ public function changeClientName(?ClientIdentity $identity, string $client_uuid, string $new_name): bool
+ {
+ if(!$this->checkPermission(Methods::CHANGE_CLIENT_NAME, $this->resolveIdentity($identity)))
+ {
+ throw new Exceptions\Standard\AccessDeniedException('You do not have sufficient permission to change the name of a client');
+ }
+
+ try
+ {
+ $this->client_manager->changeClientName($client_uuid, $new_name);
+ }
+ catch(Exception $e)
+ {
+ if(in_array($e->getCode(), ErrorCodes::ALL, true))
+ {
+ throw $e;
+ }
+
+ throw new Exceptions\Standard\InternalServerException('There was an error while changing the client name', $e);
+ }
+
+ return true;
+ }
+
+ /**
+ * @return string
+ */
+ public static function getSubprocessorPath(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'subproc';
+ }
+
}
\ No newline at end of file
diff --git a/src/FederationLib/Interfaces/CacheDriverInterface.php b/src/FederationLib/Interfaces/CacheDriverInterface.php
index 50e5503..bc8e194 100644
--- a/src/FederationLib/Interfaces/CacheDriverInterface.php
+++ b/src/FederationLib/Interfaces/CacheDriverInterface.php
@@ -3,8 +3,8 @@
namespace FederationLib\Interfaces;
use FederationLib\Classes\Configuration\CacheServerConfiguration;
- use FederationLib\Classes\Memcached;
- use FederationLib\Classes\Redis;
+ use Memcached;
+ use Redis;
interface CacheDriverInterface
{
@@ -29,6 +29,108 @@
*/
public function getConnection(): Redis|Memcached;
+ /**
+ * Returns the values of the specified key
+ *
+ * For every key that does not hold a string value or does not exist, the special value false is returned.
+ * Because of this, the operation never fails.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function get(string $key): mixed;
+
+ /**
+ * Gets a value from the hash stored at key. If the hash table doesn't exist,
+ * or the key doesn't exist, FALSE is returned
+ *
+ * @param string $key
+ * @param string $field
+ * @return mixed
+ */
+ public function hGet(string $key, string $hashKey): mixed;
+
+ /**
+ * Returns the whole hash, as an array of strings indexed by string
+ *
+ * @param string $key
+ * @return array
+ */
+ public function hGetAll(string $key): array;
+
+ /**
+ * Sets a value in the cache server
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function set(string $key, mixed $value): void;
+
+ /**
+ * Fills in a whole hash. Non-string values are converted to string, using the standard (string) cast.
+ * NULL values are stored as empty string
+ *
+ * @param string $key
+ * @param array $values
+ * @return void
+ */
+ public function hMSet(string $key, array $values): void;
+
+ /**
+ * Adds a value to the hash stored at key. If this value is already in the hash, FALSE is returned.
+ *
+ * @param string $key
+ * @param string $hash_key
+ * @param mixed $value
+ * @return void
+ */
+ public function hSet(string $key, string $hash_key, mixed $value): void;
+
+ /**
+ * Verify if the specified key exists
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function exists($key): bool;
+
+ /**
+ * Remove specified keys
+ *
+ * @param $key1
+ * @param mixed ...$other_keys
+ * @return void
+ */
+ public function delete($key1, ...$other_keys): void;
+
+ /**
+ * Sets a key to expire in a certain amount of seconds
+ *
+ * @param string $key
+ * @param int $seconds
+ * @return void
+ */
+ public function expire(string $key, int $seconds): void;
+
+ /**
+ * Determines if the cache server is available (does not necessarily mean that the server is connected)
+ * This is useful for checking if the cache server is available before attempting to use it
+ *
+ * @return bool
+ */
+ public function isAvailable(): bool;
+
+ /**
+ * Determines if the cache server is currently connected
+ * If the ping parameter is set to true, this will ping the server to ensure that it is connected
+ * otherwise it will assume the connection is stable if a connection has been established
+ *
+ * @param bool $ping This will ping the server to check if it is connected
+ * @return bool
+ */
+ public function isConnected(bool $ping=false): bool;
+
/**
* Connects to the cache server
*
@@ -43,5 +145,4 @@
*/
public function disconnect(): void;
-
}
\ No newline at end of file
diff --git a/src/FederationLib/Interfaces/CommandAppletInterface.php b/src/FederationLib/Interfaces/CommandAppletInterface.php
new file mode 100644
index 0000000..840e56f
--- /dev/null
+++ b/src/FederationLib/Interfaces/CommandAppletInterface.php
@@ -0,0 +1,24 @@
+federationLib = $federationLib;
}
+ public function registerFunctions(): void
+ {
+ if(tm::getMode() !== TamerMode::WORKER)
+ {
+ return;
+ }
+
+ tm::addFunction('client_registerClient', [$this, 'registerClient']);
+ tm::addFunction('client_getClient', [$this, 'getClient']);
+ tm::addFunction('client_changeClientName', [$this, 'changeClientName']);
+ tm::addFunction('client_changeClientDescription', [$this, 'changeClientDescription']);
+ tm::addFunction('client_changeClientPermissionRole', [$this, 'changeClientPermissionRole']);
+ tm::addFunction('client_updateClient', [$this, 'updateClient']);
+ tm::addFunction('client_updateLastSeen', [$this, 'updateLastSeen']);
+ tm::addFunction('client_listClients', [$this, 'listClients']);
+ tm::addFunction('client_getTotalClients', [$this, 'getTotalClients']);
+ tm::addFunction('client_getTotalPages', [$this, 'getTotalPages']);
+ tm::addFunction('client_deleteClient', [$this, 'deleteClient']);
+ }
+
/**
* Registers a client into the database, returns the UUID that was generated for the client.
*
- * @param Client $client
+ * @param string|null $name
+ * @param string|null $description
* @return string
* @throws DatabaseException
+ * @throws InvalidClientDescriptionException
+ * @throws InvalidClientNameException
*/
- public function registerClient(Client $client): string
+ public function registerClient(?string $name=null, ?string $description=null): string
{
$qb = Database::getConnection()->createQueryBuilder();
$qb->insert(DatabaseTables::CLIENTS);
$uuid = Uuid::v4()->toRfc4122();
- foreach($client->toArray() as $key => $value)
+ if($name === null)
{
- switch($key)
+ $name = Utilities::generateName(4);
+ }
+ else
+ {
+ if(!Validate::clientName($name))
{
- 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;
+ throw new InvalidClientNameException(sprintf('Invalid client name: %s', $name));
}
}
+ if($description !== null && strlen($description) > 128)
+ {
+ throw new InvalidClientDescriptionException(sprintf('Invalid client description: %s', $description));
+ }
+
+ $qb->setValue('uuid', $qb->createNamedParameter($uuid));
+ $qb->setValue('name', $qb->createNamedParameter($name));
+ $qb->setValue('description', $qb->createNamedParameter($description, (is_null($description) ? ParameterType::NULL : ParameterType::STRING)));
+ $qb->setValue('secret_totp', $qb->createNamedParameter(Security::generateSecret()));
+ $qb->setValue('flags', $qb->createNamedParameter(null, ParameterType::NULL));
+
try
{
$qb->executeStatement();
@@ -105,11 +110,6 @@
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;
}
@@ -117,38 +117,24 @@
/**
* Returns an existing client from the database.
*
- * @param string|Client $uuid
+ * @param string|Client $client_uuid
* @return Client
* @throws ClientNotFoundException
* @throws DatabaseException
*/
- public function getClient(string|Client $uuid): Client
+ public function getClient(string|Client $client_uuid): Client
{
- if($uuid instanceof Client)
+ if($client_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()));
- }
+ $client_uuid = $client_uuid->getUuid();
}
$qb = Database::getConnection()->createQueryBuilder();
$qb->select('*');
$qb->from(DatabaseTables::CLIENTS);
$qb->where('uuid = :uuid');
- $qb->setParameter('uuid', $uuid);
+ $qb->setParameter('uuid', $client_uuid);
+ $qb->setMaxResults(1);
try
{
@@ -156,7 +142,7 @@
if($result->rowCount() === 0)
{
- throw new ClientNotFoundException($uuid);
+ throw new ClientNotFoundException($client_uuid);
}
$client = Client::fromArray($result->fetchAssociative());
@@ -170,22 +156,166 @@
throw new DatabaseException('Failed to get Client: ' . $e->getMessage(), $e);
}
- if(Configuration::isRedisCacheClientObjectsEnabled())
+ return $client;
+ }
+
+ /**
+ * Changes the name of a client.
+ *
+ * @param string|Client $client_uuid
+ * @param string|null $name
+ * @return void
+ * @throws ClientNotFoundException
+ * @throws DatabaseException
+ * @throws InvalidClientNameException
+ */
+ public function changeClientName(string|Client $client_uuid, ?string $name=null): void
+ {
+ if($client_uuid instanceof Client)
{
- try
+ $client_uuid = $client_uuid->getUuid();
+ }
+
+ if($name === null)
+ {
+ $name = Utilities::generateName(4);
+ }
+ else
+ {
+ if(!Validate::clientName($name))
{
- 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()));
+ throw new InvalidClientNameException(sprintf('Invalid client name: %s', $name));
}
}
- return $client;
+ $qb = Database::getConnection()->createQueryBuilder();
+ $qb->update(DatabaseTables::CLIENTS);
+ $qb->set('name', ':name');
+ $qb->setParameter('name', $name);
+ $qb->set('updated_timestamp', ':updated_timestamp');
+ $qb->setParameter('updated_timestamp', time(), ParameterType::INTEGER);
+ $qb->where('uuid = :uuid');
+ $qb->setParameter('uuid', $client_uuid);
+ $qb->setMaxResults(1);
+
+ try
+ {
+ $affected_rows = $qb->executeStatement();
+ }
+ catch(Exception $e)
+ {
+ throw new DatabaseException('Failed to change client name: ' . $e->getMessage(), $e);
+ }
+
+ if($affected_rows === 0)
+ {
+ throw new ClientNotFoundException($client_uuid);
+ }
+
+ Log::verbose('net.nosial.federationlib', sprintf('Changed client name for client %s to %s', $client_uuid, $name));
}
+ /**
+ * Changes the description of a client
+ *
+ * @param string|Client $client_uuid
+ * @param string|null $description
+ * @return void
+ * @throws ClientNotFoundException
+ * @throws DatabaseException
+ * @throws InvalidClientDescriptionException
+ */
+ public function changeClientDescription(string|Client $client_uuid, ?string $description=null): void
+ {
+ if($client_uuid instanceof Client)
+ {
+ $client_uuid = $client_uuid->getUuid();
+ }
+
+ if($description !== null && strlen($description) > 128)
+ {
+ throw new InvalidClientDescriptionException(sprintf('Invalid client description: %s', $description));
+ }
+
+ $qb = Database::getConnection()->createQueryBuilder();
+ $qb->update(DatabaseTables::CLIENTS);
+ $qb->set('description', ':description');
+ $qb->setParameter('description', $description, (is_null($description) ? ParameterType::NULL : ParameterType::STRING));
+ $qb->set('updated_timestamp', ':updated_timestamp');
+ $qb->setParameter('updated_timestamp', time(), ParameterType::INTEGER);
+ $qb->where('uuid = :uuid');
+ $qb->setParameter('uuid', $client_uuid);
+ $qb->setMaxResults(1);
+
+ try
+ {
+ $affected_rows = $qb->executeStatement();
+ }
+ catch(Exception $e)
+ {
+ throw new DatabaseException('Failed to change client description: ' . $e->getMessage(), $e);
+ }
+
+ if($affected_rows === 0)
+ {
+ throw new ClientNotFoundException($client_uuid);
+ }
+
+ Log::verbose('net.nosial.federationlib', sprintf('Changed client description for client %s to %s', $client_uuid, $description));
+ }
+
+ /**
+ * Updates the permission role of a client.
+ *
+ * @param string|Client $client_uuid
+ * @param int $permission_role
+ * @return void
+ * @throws ClientNotFoundException
+ * @throws DatabaseException
+ * @throws InvalidPermissionRoleException
+ */
+ public function changeClientPermissionRole(string|Client $client_uuid, int $permission_role): void
+ {
+ if($client_uuid instanceof Client)
+ {
+ $client_uuid = $client_uuid->getUuid();
+ }
+
+ if(!Validate::permissionRole($permission_role))
+ {
+ throw new InvalidPermissionRoleException(sprintf('Invalid permission role: %s', $permission_role));
+ }
+
+ $time = time();
+
+ $qb = Database::getConnection()->createQueryBuilder();
+ $qb->update(DatabaseTables::CLIENTS);
+ $qb->set('permission_role', ':permission_role');
+ $qb->setParameter('permission_role', $permission_role, ParameterType::INTEGER);
+ $qb->set('updated_timestamp', ':updated_timestamp');
+ $qb->setParameter('updated_timestamp', $time, ParameterType::INTEGER);
+ $qb->where('uuid = :uuid');
+ $qb->setParameter('uuid', $client_uuid);
+ $qb->setMaxResults(1);
+
+ try
+ {
+ $affected_rows = $qb->executeStatement();
+ }
+ catch(Exception $e)
+ {
+ throw new DatabaseException('Failed to change client permission role: ' . $e->getMessage(), $e);
+ }
+
+ if($affected_rows === 0)
+ {
+ throw new ClientNotFoundException($client_uuid);
+ }
+
+ Log::verbose('net.nosial.federationlib', sprintf('Changed client permission role for client %s to %s', $client_uuid, $permission_role));
+ }
+
+
/**
* 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.
diff --git a/src/FederationLib/Managers/PeerManager.php b/src/FederationLib/Managers/PeerManager.php
index 29e906d..ea8af86 100644
--- a/src/FederationLib/Managers/PeerManager.php
+++ b/src/FederationLib/Managers/PeerManager.php
@@ -2,171 +2,22 @@
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;
+ private FederationLib $federationLib;
/**
+ * PeerManager constructor.
+ *
* @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);
- }
- }
}
\ No newline at end of file
diff --git a/src/FederationLib/Managers/RedisConnectionManager.php b/src/FederationLib/Managers/RedisConnectionManager.php
new file mode 100644
index 0000000..ed2c916
--- /dev/null
+++ b/src/FederationLib/Managers/RedisConnectionManager.php
@@ -0,0 +1,177 @@
+getName()] = new Redis($configuration);
+ }
+ }
+
+ // If the name isn't null or "any", return the connection with that name
+ if($name !== null || strtolower($name) !== 'any')
+ {
+ if(!isset(self::$connections[$name]))
+ {
+ if($fallback !== null)
+ {
+ return self::getConnection($fallback);
+ }
+
+ throw new InvalidArgumentException("Redis connection with name '$name' not found");
+ }
+
+ if(!self::$connections[$name]->isAvailable())
+ {
+ if($fallback !== null)
+ {
+ return self::getConnection($fallback);
+ }
+
+ throw new CacheConnectionException("Redis connection with name '$name' is not available");
+ }
+
+ try
+ {
+ return self::$connections[$name]->getConnection();
+ }
+ catch(Exception $e)
+ {
+ if($fallback !== null)
+ {
+ return self::getConnection($fallback);
+ }
+
+ throw new CacheConnectionException(sprintf("Failed to retrieve the connection for \'%s\'", $name), 0, $e);
+ }
+ }
+
+ // Assuming we're here, we're looking for any connection
+
+ // Build the weights array
+ $weights = [];
+ /** @var Redis $connection */
+ foreach(self::$connections as $connection)
+ {
+ if($connection->isAvailable())
+ {
+ $priority = $connection->getConfiguration()->getPriority();
+
+ // Calculate the priority based on the connection state and the configuration
+ if(Configuration::getCacheErrorConnectionPriority() !== 0 && $connection->isConnectionError())
+ {
+ $priority = ((Configuration::getCacheErrorConnectionPriority()) + ($priority));
+ }
+ elseif(Configuration::getCacheOpenedConnectionPriority() !== 0 && $connection->isConnected())
+ {
+ $priority = ((Configuration::getCacheOpenedConnectionPriority()) + ($priority));
+ }
+
+ if((int)$priority > 100)
+ {
+ $priority = 100;
+ }
+ elseif((int)$priority < 0)
+ {
+ $priority = 0;
+ }
+
+ $weights[$connection->getConfiguration()->getName()] = (int)$priority;
+ }
+ }
+
+ if(count($weights) === 0)
+ {
+ // If there are no available connections, this may be based off of the configuration
+ // In this case, resort to the default.
+ /** @var Redis $connection */
+ foreach(self::$connections as $connection)
+ {
+ if($connection->isAvailable())
+ {
+ $weights[$connection->getConfiguration()->getName()] = $connection->getConfiguration()->getPriority();
+ }
+ }
+
+ // If there are still no available connections, throw an exception
+ // It's clearly the user's fault at this point lol
+ if (count($weights) === 0)
+ {
+ if($fallback !== null)
+ {
+ return self::getConnection($fallback);
+ }
+
+ throw new CacheConnectionException("No available Redis connections");
+ }
+
+ if(count($weights) === 1)
+ {
+ // If there's only one available connection, just use that lmao ez
+ return self::$connections[array_key_first($weights)]->getConnection();
+ }
+ }
+ elseif(count($weights) === 1)
+ {
+ // Same as above
+ return self::$connections[array_key_first($weights)]->getConnection();
+ }
+
+ $selected_connection = Utilities::weightedRandomPick($weights);
+
+ try
+ {
+ return self::$connections[$selected_connection]->getConnection();
+ }
+ catch(Exception $e)
+ {
+ if($fallback !== null)
+ {
+ // After all that, at least we have a fallback
+ return self::getConnection($fallback);
+ }
+
+ }
+ finally
+ {
+ // Or not :(
+ throw new CacheConnectionException(sprintf("Failed to retrieve the connection for \'%s\'", $selected_connection), 0, $e);
+ }
+
+ // Voila! je suis fier de ça
+ }
+ }
\ No newline at end of file
diff --git a/src/FederationLib/Objects/Client.php b/src/FederationLib/Objects/Client.php
index 2ec4897..01e5bb7 100755
--- a/src/FederationLib/Objects/Client.php
+++ b/src/FederationLib/Objects/Client.php
@@ -6,6 +6,7 @@
use FederationCLI\Utilities;
use FederationLib\Classes\Security;
+ use FederationLib\Enums\Standard\PermissionRole;
use FederationLib\Interfaces\SerializableObjectInterface;
class Client implements SerializableObjectInterface
@@ -37,13 +38,9 @@
/**
* @var int
+ * @see PermissionRole
*/
- private $query_permission;
-
- /**
- * @var int
- */
- private $update_permission;
+ private $permission_role;
/**
* @var string[]
@@ -72,8 +69,7 @@
{
$this->enabled = true;
$this->flags = [];
- $this->query_permission = 1;
- $this->update_permission = 0;
+ $this->permission_role = PermissionRole::CLIENT;
}
/**
@@ -95,15 +91,9 @@
}
/**
- * @param bool $enabled
- */
- public function setEnabled(bool $enabled): void
- {
- $this->enabled = $enabled;
- }
-
- /**
- * @return string|null
+ * Returns the client's name.
+ *
+ * @return string
*/
public function getName(): string
{
@@ -111,14 +101,8 @@
}
/**
- * @param string|null $name
- */
- public function setName(?string $name): void
- {
- $this->name = $name;
- }
-
- /**
+ * Optional. Returns the client's description.
+ *
* @return string|null
*/
public function getDescription(): ?string
@@ -127,32 +111,13 @@
}
/**
- * @param string|null $description
- */
- public function setDescription(?string $description): void
- {
- $this->description = $description;
- }
-
- /**
- * Enables authentication for the client, returns the secret.
+ * Indicates whether or not the client requires authentication.
*
- * @return string
+ * @return bool
*/
- public function enableAuthentication(): string
+ public function requiresAuthentication(): bool
{
- $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;
+ return $this->secret_totp !== null;
}
/**
@@ -185,39 +150,9 @@
*
* @return int
*/
- public function getQueryPermission(): int
+ public function getPermissionRole(): 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;
+ return $this->permission_role;
}
/**
@@ -230,42 +165,6 @@
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.
*
@@ -327,8 +226,7 @@
'name' => $this->name,
'description' => $this->description,
'secret_totp' => $this->secret_totp,
- 'query_permission' => $this->query_permission,
- 'update_permission' => $this->update_permission,
+ 'permission_role' => $this->permission_role,
'flags' => $flags,
'created_timestamp' => $this->created_timestamp,
'updated_timestamp' => $this->updated_timestamp,
@@ -351,8 +249,7 @@
$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;
+ $client->permission_role = $array['permission_role'] ?? PermissionRole::CLIENT;
if(isset($array['flags']))
{
$client->flags = explode(',', $array['flags']);
diff --git a/src/FederationLib/Objects/InvokeResults.php b/src/FederationLib/Objects/InvokeResults.php
new file mode 100644
index 0000000..441ff45
--- /dev/null
+++ b/src/FederationLib/Objects/InvokeResults.php
@@ -0,0 +1,50 @@
+exit_code = $exit_code;
+ $this->output = $output;
+ }
+
+ /**
+ * Returns the exit code of the command.
+ *
+ * @return int
+ */
+ public function getExitCode(): int
+ {
+ return $this->exit_code;
+ }
+
+ /**
+ * Returns the output of the command.
+ *
+ * @return string
+ */
+ public function getOutput(): string
+ {
+ return $this->output;
+ }
+ }
\ No newline at end of file
diff --git a/src/FederationLib/Objects/ParsedFederatedAddress.php b/src/FederationLib/Objects/ParsedFederatedAddress.php
index 0388d96..f10f275 100644
--- a/src/FederationLib/Objects/ParsedFederatedAddress.php
+++ b/src/FederationLib/Objects/ParsedFederatedAddress.php
@@ -4,6 +4,8 @@
namespace FederationLib\Objects;
+ use InvalidArgumentException;
+
class ParsedFederatedAddress
{
/**
@@ -30,6 +32,12 @@
{
preg_match("/(?[a-z0-9]+)\.(?[a-z0-9]+):(?.+)/", $federated_address, $matches);
+ // Validate the federated address
+ if (empty($matches))
+ {
+ throw new InvalidArgumentException('Invalid Federated Address');
+ }
+
$this->source = $matches['source'];
$this->peer_type = $matches['type'];
$this->unique_identifier = $matches['id'];
diff --git a/src/FederationLib/Objects/ResolvedIdentity.php b/src/FederationLib/Objects/ResolvedIdentity.php
new file mode 100644
index 0000000..38c8cba
--- /dev/null
+++ b/src/FederationLib/Objects/ResolvedIdentity.php
@@ -0,0 +1,98 @@
+client = $client;
+ $this->peer = $peer;
+
+ if($this->client === null)
+ {
+ if(!$allow_root)
+ {
+ throw new AccessDeniedException('Missing Client Identity');
+ }
+
+ $this->permission_role = PermissionRole::ROOT;
+ return;
+ }
+
+ if(!Configuration::strictPermissionEnabled())
+ {
+ if($this->peer === null)
+ {
+ $this->permission_role = $this->client->getPermissionRole();
+ return;
+ }
+
+ $this->permission_role = $this->peer->getPermissionRole();
+ return;
+ }
+
+ if($this->peer === null)
+ {
+ $this->permission_role = $this->client->getPermissionRole();
+ return;
+ }
+
+ if($this->client->getPermissionRole() > $this->peer->getPermissionRole())
+ {
+ $this->permission_role = $this->client->getPermissionRole();
+ return;
+ }
+
+ $this->permission_role = $this->peer->getPermissionRole();
+ }
+
+ /**
+ * @return Client|null
+ */
+ public function getClient(): ?Client
+ {
+ return $this->client;
+ }
+
+ /**
+ * @return Peer|null
+ */
+ public function getPeer(): ?Peer
+ {
+ return $this->peer;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPermissionRole(): int
+ {
+ return $this->permission_role;
+ }
+ }
\ No newline at end of file
diff --git a/src/FederationLib/Objects/Standard/Client.php b/src/FederationLib/Objects/Standard/Client.php
new file mode 100644
index 0000000..f34f310
--- /dev/null
+++ b/src/FederationLib/Objects/Standard/Client.php
@@ -0,0 +1,173 @@
+uuid = $client->getUuid();
+ $this->enabled = $client->isEnabled();
+ $this->name = $client->getName();
+ $this->description = $client->getDescription();
+ $this->permission_role = $client->getPermissionRole();
+ $this->created_timestamp = $client->getCreatedTimestamp();
+ $this->updated_timestamp = $client->getUpdatedTimestamp();
+ $this->seen_timestamp = $client->getSeenTimestamp();
+ }
+
+ /**
+ * @return string
+ */
+ public function getUuid(): string
+ {
+ return $this->uuid;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnabled(): bool
+ {
+ return $this->enabled;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getDescription(): ?string
+ {
+ return $this->description;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPermissionRole(): int
+ {
+ return $this->permission_role;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCreatedTimestamp(): int
+ {
+ return $this->created_timestamp;
+ }
+
+ /**
+ * @return int
+ */
+ public function getUpdatedTimestamp(): int
+ {
+ return $this->updated_timestamp;
+ }
+
+ /**
+ * @return int
+ */
+ public function getSeenTimestamp(): int
+ {
+ return $this->seen_timestamp;
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @return array
+ */
+ public function toArray(): array
+ {
+ return [
+ 'uuid' => $this->uuid,
+ 'enabled' => $this->enabled,
+ 'name' => $this->name,
+ 'description' => $this->description,
+ 'created_timestamp' => $this->created_timestamp,
+ 'updated_timestamp' => $this->updated_timestamp,
+ 'seen_timestamp' => $this->seen_timestamp
+ ];
+ }
+
+ /**
+ * Constructs an object from an array representation
+ *
+ * @param array $array
+ * @return SerializableObjectInterface
+ */
+ public static function fromArray(array $array): SerializableObjectInterface
+ {
+ $object = new self();
+
+ $object->uuid = $array['uuid'];
+ $object->enabled = $array['enabled'];
+ $object->name = $array['name'];
+ $object->description = $array['description'];
+ $object->created_timestamp = $array['created_timestamp'];
+ $object->updated_timestamp = $array['updated_timestamp'];
+ $object->seen_timestamp = $array['seen_timestamp'];
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/FederationLib/Objects/Standard/ClientIdentity.php b/src/FederationLib/Objects/Standard/ClientIdentity.php
new file mode 100644
index 0000000..2b014df
--- /dev/null
+++ b/src/FederationLib/Objects/Standard/ClientIdentity.php
@@ -0,0 +1,84 @@
+client_uuid;
+ }
+
+ /**
+ * Optional. Returns the client TOTP signature
+ *
+ * @return string|null
+ */
+ public function getClientTotpSignature(): ?string
+ {
+ return $this->client_totp_signature;
+ }
+
+ /**
+ * Optional. Returns the peer
+ *
+ * @return string|null
+ */
+ public function getPeer(): ?string
+ {
+ return $this->peer;
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @return array
+ */
+ public function toArray(): array
+ {
+ return [
+ 'client_uuid' => $this->client_uuid,
+ 'client_totp_signature' => $this->client_totp_signature,
+ 'peer' => $this->peer
+ ];
+ }
+
+ /**
+ * Constructs the object from an array representation
+ *
+ * @param array $array
+ * @return ClientIdentity
+ */
+ public static function fromArray(array $array): ClientIdentity
+ {
+ $object = new self();
+ $object->client_uuid = $array['client_uuid'] ?? null;
+ $object->client_totp_signature = $array['client_totp_signature'] ?? null;
+ $object->peer = $array['peer'] ?? null;
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/FederationLib/subproc b/src/FederationLib/subproc
new file mode 100644
index 0000000..f218f25
--- /dev/null
+++ b/src/FederationLib/subproc
@@ -0,0 +1,22 @@
+registerFunctions();
+
+ try
+ {
+ \TamerLib\tm::run();
+ }
+ catch(Exception $e)
+ {
+ \LogLib\Log::error(\FederationLib\Enums\Misc::FEDERATIONLIB, $e->getMessage(), $e);
+ }
+ finally
+ {
+ exit(0);
+ }