Add Operator management classes and exception handling
Some checks are pending
CI / release (push) Waiting to run
CI / debug (push) Waiting to run
CI / check-phpunit (push) Waiting to run
CI / check-phpdoc (push) Waiting to run
CI / generate-phpdoc (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / release-documentation (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions

This commit is contained in:
netkas 2025-05-29 17:22:40 -04:00
parent 7c7947be9c
commit 0071c4ebea
Signed by: netkas
GPG key ID: 4D8629441B76E4CC
6 changed files with 572 additions and 0 deletions

7
.idea/sqldialects.xml generated Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/src/FederationServer/Classes/Managers/OperatorManager.php" dialect="MariaDB" />
<file url="file://$PROJECT_DIR$/src/FederationServer/Classes/Resources/operators.sql" dialect="MariaDB" />
</component>
</project>

View file

@ -0,0 +1,319 @@
<?php
namespace FederationServer\Classes\Managers;
use FederationServer\Classes\DatabaseConnection;
use FederationServer\Classes\Utilities;
use FederationServer\Exceptions\DatabaseOperationException;
use FederationServer\Objects\OperatorRecord;
use InvalidArgumentException;
use PDOException;
use Symfony\Component\Uid\Uuid;
class OperatorManager
{
/**
* Create a new operator with a unique API key.
*
* @param string $name The name of the operator.
* @return string The generated API key for the operator.
* @throws InvalidArgumentException If the name is empty or exceeds 255 characters.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function createOperator(string $name): string
{
if(empty($name))
{
throw new InvalidArgumentException('Operator name cannot be empty.');
}
if(strlen($name) > 255)
{
throw new InvalidArgumentException('Operator name cannot exceed 255 characters.');
}
$uuid = Uuid::v7()->toRfc4122();
$apiKey = Utilities::generateString();
try
{
$stmt = DatabaseConnection::getConnection()->prepare("INSERT INTO operators (uuid, api_key, name) VALUES (:uuid, :api_key, :name)");
$stmt->bindParam(':uuid', $uuid);
$stmt->bindParam(':api_key', $apiKey);
$stmt->bindParam(':name', $name);
$stmt->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to create operator '%s'", $name), 0, $e);
}
return $uuid;
}
/**
* Retrieve an operator by their UUID.
*
* @param string $uuid The UUID of the operator.
* @return OperatorRecord|null The operator record if found, null otherwise.
* @throws InvalidArgumentException If the UUID is empty.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function getOperator(string $uuid): ?OperatorRecord
{
if(empty($uuid))
{
throw new InvalidArgumentException('Operator UUID cannot be empty.');
}
try
{
$stmt = DatabaseConnection::getConnection()->prepare("SELECT * FROM operators WHERE uuid=:uuid");
$stmt->bindParam(':uuid', $uuid);
$stmt->execute();
$data = $stmt->fetch();
if($data === false)
{
return null; // No operator found with the given UUID
}
return new OperatorRecord($data);
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to retrieve operator with UUID '%s'", $uuid), 0, $e);
}
}
/**
* Retrieve an operator by their API key.
*
* @param string $apiKey The API key of the operator.
* @return OperatorRecord|null The operator record if found, null otherwise.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function getOperatorByApiKey(string $apiKey): ?OperatorRecord
{
if(empty($apiKey))
{
throw new InvalidArgumentException('API key cannot be empty.');
}
try
{
$stmt = DatabaseConnection::getConnection()->prepare("SELECT * FROM operators WHERE api_key=:api_key");
$stmt->bindParam(':api_key', $apiKey);
$stmt->execute();
$data = $stmt->fetch();
if($data === false)
{
return null; // No operator found with the given API key
}
return new OperatorRecord($data);
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to retrieve operator with API key '%s'", $apiKey), 0, $e);
}
}
/**
* Disable an operator by their UUID.
*
* @param string $uuid The UUID of the operator.
* @throws InvalidArgumentException If the UUID is empty.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function disableOperator(string $uuid): void
{
if(empty($uuid))
{
throw new InvalidArgumentException('Operator UUID cannot be empty.');
}
try
{
$stmt = DatabaseConnection::getConnection()->prepare("UPDATE operators SET disabled=1 WHERE uuid=:uuid");
$stmt->bindParam(':uuid', $uuid);
$stmt->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to disable operator with UUID '%s'", $uuid), 0, $e);
}
}
/**
* Enable an operator by their UUID.
*
* @param string $uuid The UUID of the operator.
* @throws InvalidArgumentException If the UUID is empty.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function enableOperator(string $uuid): void
{
if(empty($uuid))
{
throw new InvalidArgumentException('Operator UUID cannot be empty.');
}
try
{
$stmt = DatabaseConnection::getConnection()->prepare("UPDATE operators SET disabled=0 WHERE uuid=:uuid");
$stmt->bindParam(':uuid', $uuid);
$stmt->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to enable operator with UUID '%s'", $uuid), 0, $e);
}
}
/**
* Delete an operator by their UUID.
*
* @param string $uuid The UUID of the operator.
* @throws InvalidArgumentException If the UUID is empty.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function deleteOperator(string $uuid): void
{
if(empty($uuid))
{
throw new InvalidArgumentException('Operator UUID cannot be empty.');
}
try
{
$stmt = DatabaseConnection::getConnection()->prepare("DELETE FROM operators WHERE uuid=:uuid");
$stmt->bindParam(':uuid', $uuid);
$stmt->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to delete operator with UUID '%s'", $uuid), 0, $e);
}
}
/**
* Refresh the API key for an operator.
*
* @param string $uuid The UUID of the operator.
* @return string The new API key for the operator.
* @throws InvalidArgumentException If the UUID is empty.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function refreshApiKey(string $uuid): string
{
if(empty($uuid))
{
throw new InvalidArgumentException('Operator UUID cannot be empty.');
}
$newApiKey = Utilities::generateString();
try
{
$stmt = DatabaseConnection::getConnection()->prepare("UPDATE operators SET api_key=:api_key WHERE uuid=:uuid");
$stmt->bindParam(':api_key', $newApiKey);
$stmt->bindParam(':uuid', $uuid);
$stmt->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to refresh API key for operator with UUID '%s'", $uuid), 0, $e);
}
return $newApiKey;
}
/**
* Set the management permissions for an operator.
*
* @param string $uuid The UUID of the operator.
* @param bool $canManageOperators True if the operator can manage other operators, false otherwise.
* @throws InvalidArgumentException If the UUID is empty.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function setManageOperators(string $uuid, bool $canManageOperators): void
{
if(empty($uuid))
{
throw new InvalidArgumentException('Operator UUID cannot be empty.');
}
try
{
$stmt = DatabaseConnection::getConnection()->prepare("UPDATE operators SET manage_operators=:manage_operators WHERE uuid=:uuid");
$stmt->bindParam(':manage_operators', $canManageOperators, \PDO::PARAM_BOOL);
$stmt->bindParam(':uuid', $uuid);
$stmt->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to set operator management permissions for operator with UUID '%s'", $uuid), 0, $e);
}
}
/**
* Set the blacklist management permissions for an operator.
*
* @param string $uuid The UUID of the operator.
* @param bool $canManageBlacklist True if the operator can manage the blacklist, false otherwise.
* @throws InvalidArgumentException If the UUID is empty.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function setManageBlacklist(string $uuid, bool $canManageBlacklist): void
{
if(empty($uuid))
{
throw new InvalidArgumentException('Operator UUID cannot be empty.');
}
try
{
$stmt = DatabaseConnection::getConnection()->prepare("UPDATE operators SET manage_blacklist=:manage_blacklist WHERE uuid=:uuid");
$stmt->bindParam(':manage_blacklist', $canManageBlacklist, \PDO::PARAM_BOOL);
$stmt->bindParam(':uuid', $uuid);
$stmt->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to set blacklist management permissions for operator with UUID '%s'", $uuid), 0, $e);
}
}
/**
* Set the client status for an operator.
*
* @param string $uuid The UUID of the operator.
* @param bool $isClient True if the operator is a client, false otherwise.
* @throws InvalidArgumentException If the UUID is empty.
* @throws DatabaseOperationException If there is an error during the database operation.
*/
public static function setClient(string $uuid, bool $isClient): void
{
if(empty($uuid))
{
throw new InvalidArgumentException('Operator UUID cannot be empty.');
}
try
{
$stmt = DatabaseConnection::getConnection()->prepare("UPDATE operators SET is_client=:is_client WHERE uuid=:uuid");
$stmt->bindParam(':is_client', $isClient, \PDO::PARAM_BOOL);
$stmt->bindParam(':uuid', $uuid);
$stmt->execute();
}
catch (PDOException $e)
{
throw new DatabaseOperationException(sprintf("Failed to set client status for operator with UUID '%s'", $uuid), 0, $e);
}
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace FederationServer\Classes;
class Utilities
{
/*
* Generate a random string of specified length.
*
* @param int $length Length of the random string to generate.
* @return string Randomly generated string.
*/
public static function generateString(int $length=32): string
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++)
{
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace FederationServer\Exceptions;
use Throwable;
class DatabaseOperationException extends \Exception
{
/**
* DatabaseOperationException constructor.
*
* @param string $message The exception message.
* @param int $code The exception code.
* @param Throwable|null $previous The previous throwable used for the exception chaining.
*/
public function __construct(string $message="", int $code=0, ?Throwable $previous=null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace FederationServer\Interfaces;
interface SerializableInterface
{
/**
* Convert the object to an associative array representation.
*
* @return array An associative array representing the object's state.
*/
public function toArray(): array;
/**
* Create an instance of the object from an associative array.
*
* @param array $array An associative array containing the object's state.
* @return SerializableInterface An instance of the object.
*/
public static function fromArray(array $array): SerializableInterface;
}

View file

@ -0,0 +1,179 @@
<?php
namespace FederationServer\Objects;
use DateTime;
use FederationServer\Interfaces\SerializableInterface;
class OperatorRecord implements SerializableInterface
{
private string $uuid;
private string $apiKey;
private string $name;
private bool $disabled;
private bool $manageOperators;
private bool $manageBlacklist;
private bool $isClient;
private int $created;
private int $updated;
/**
* OperatorRecord constructor.
*
* @param array $data Associative array of operator data.
*/
public function __construct(array $data)
{
$this->uuid = $data['uuid'] ?? '';
$this->apiKey = $data['api_key'] ?? '';
$this->name = $data['name'] ?? '';
$this->disabled = (bool)$data['disabled'] ?? false;
$this->manageOperators = (bool)$data['manage_operators'] ?? false;
$this->manageBlacklist = (bool)$data['manage_blacklist'] ?? false;
$this->isClient = (bool)$data['is_client'] ?? false;
$this->created = (int)$data['created'] ?? time();
$this->updated = (int)$data['updated'] ?? time();
}
/**
* Get the UUID of the operator.
*
* @return string
*/
public function getUuid(): string
{
return $this->uuid;
}
/**
* Get the API key of the operator.
*
* @return string
*/
public function getApiKey(): string
{
return $this->apiKey;
}
/**
* Get the name of the operator.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Check if the operator is disabled.
*
* @return bool
*/
public function isDisabled(): bool
{
return $this->disabled;
}
/**
* Check if the operator can manage other operators.
*
* @return bool
*/
public function canManageOperators(): bool
{
return $this->manageOperators;
}
/**
* Check if the operator can manage the blacklist.
*
* @return bool
*/
public function canManageBlacklist(): bool
{
return $this->manageBlacklist;
}
/**
* Check if the operator is a client.
*
* @return bool
*/
public function isClient(): bool
{
return $this->isClient;
}
/**
* Get the creation timestamp.
*
* @return int
*/
public function getCreated(): int
{
return $this->created;
}
/**
* Get the last updated timestamp.
*
* @return int
*/
public function getUpdated(): int
{
return $this->updated;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return [
'uuid' => $this->uuid,
'api_key' => $this->apiKey,
'name' => $this->name,
'disabled' => $this->disabled,
'manage_operators' => $this->manageOperators,
'manage_blacklist' => $this->manageBlacklist,
'is_client' => $this->isClient,
'created' => $this->created,
'updated' => $this->updated
];
}
/**
* @inheritDoc
*/
public static function fromArray(array $array): SerializableInterface
{
if(isset($array['created']) )
{
if(is_string($array['created']))
{
$array['created'] = strtotime($array['created']);
}
if($array['created'] instanceof DateTime)
{
$array['created'] = $array['created']->getTimestamp();
}
}
if(isset($array['updated']) )
{
if(is_string($array['updated']))
{
$array['updated'] = strtotime($array['updated']);
}
if($array['updated'] instanceof DateTime)
{
$array['updated'] = $array['updated']->getTimestamp();
}
}
$object = new self($array);
}
}