Enhance BlacklistManager: add evidence parameter and UUID generation for blacklist entries
This commit is contained in:
parent
fc6014b37e
commit
f4536df74f
7 changed files with 172 additions and 18 deletions
|
@ -36,6 +36,7 @@
|
||||||
self::$configuration->setDefault('server.public_audit_entries', array_map(fn($type) => $type->value, AuditLogType::cases()));
|
self::$configuration->setDefault('server.public_audit_entries', array_map(fn($type) => $type->value, AuditLogType::cases()));
|
||||||
self::$configuration->setDefault('server.public_evidence', true);
|
self::$configuration->setDefault('server.public_evidence', true);
|
||||||
self::$configuration->setDefault('server.public_blacklist', true);
|
self::$configuration->setDefault('server.public_blacklist', true);
|
||||||
|
self::$configuration->setDefault('server.min_blacklist_time', 1800);
|
||||||
|
|
||||||
self::$configuration->setDefault('database.host', '127.0.0.1');
|
self::$configuration->setDefault('database.host', '127.0.0.1');
|
||||||
self::$configuration->setDefault('database.port', 3306);
|
self::$configuration->setDefault('database.port', 3306);
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
private array $publicAuditEntries;
|
private array $publicAuditEntries;
|
||||||
private bool $publicEvidence;
|
private bool $publicEvidence;
|
||||||
private bool $publicBlacklist;
|
private bool $publicBlacklist;
|
||||||
|
private int $minBlacklistTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServerConfiguration constructor.
|
* ServerConfiguration constructor.
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
$this->publicAuditEntries = array_map(fn($type) => AuditLogType::from($type), $config['public_audit_entries'] ?? []);
|
$this->publicAuditEntries = array_map(fn($type) => AuditLogType::from($type), $config['public_audit_entries'] ?? []);
|
||||||
$this->publicEvidence = $config['public_evidence'] ?? true;
|
$this->publicEvidence = $config['public_evidence'] ?? true;
|
||||||
$this->publicBlacklist = $config['public_blacklist'] ?? true;
|
$this->publicBlacklist = $config['public_blacklist'] ?? true;
|
||||||
|
$this->minBlacklistTime = $config['min_blacklist_time'] ?? 1800;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,4 +188,16 @@
|
||||||
{
|
{
|
||||||
return $this->publicBlacklist;
|
return $this->publicBlacklist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum allowed time that a blacklist could be set to expire, for example
|
||||||
|
* 1800 = 30 Minutes, if a blacklist is set to expire within 30 minutes or more, it's valid, otherwise
|
||||||
|
* anything less than that if it isn't null would be considered invalid.
|
||||||
|
*
|
||||||
|
* @return int The number of seconds allowed
|
||||||
|
*/
|
||||||
|
public function getMinBlacklistTime(): int
|
||||||
|
{
|
||||||
|
return $this->minBlacklistTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
|
|
||||||
use FederationServer\Classes\DatabaseConnection;
|
use FederationServer\Classes\DatabaseConnection;
|
||||||
use FederationServer\Classes\Enums\BlacklistType;
|
use FederationServer\Classes\Enums\BlacklistType;
|
||||||
|
use FederationServer\Classes\Validate;
|
||||||
use FederationServer\Exceptions\DatabaseOperationException;
|
use FederationServer\Exceptions\DatabaseOperationException;
|
||||||
use FederationServer\Objects\BlacklistRecord;
|
use FederationServer\Objects\BlacklistRecord;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use PDO;
|
use PDO;
|
||||||
use PDOException;
|
use PDOException;
|
||||||
|
use Symfony\Component\Uid\UuidV4;
|
||||||
|
|
||||||
class BlacklistManager
|
class BlacklistManager
|
||||||
{
|
{
|
||||||
|
@ -19,10 +21,12 @@
|
||||||
* @param string $operator The UUID of the operator performing the blacklisting.
|
* @param string $operator The UUID of the operator performing the blacklisting.
|
||||||
* @param BlacklistType $type The type of blacklist action.
|
* @param BlacklistType $type The type of blacklist action.
|
||||||
* @param int|null $expires Optional expiration time in Unix timestamp, null for permanent blacklisting.
|
* @param int|null $expires Optional expiration time in Unix timestamp, null for permanent blacklisting.
|
||||||
|
* @param string|null $evidence Optional evidence UUID, must be a valid UUID if provided.
|
||||||
|
* @return string The UUID of the created blacklist entry.
|
||||||
* @throws InvalidArgumentException If the entity or operator is empty, or if expires is in the past.
|
* @throws InvalidArgumentException If the entity or operator is empty, or if expires is in the past.
|
||||||
* @throws DatabaseOperationException If there is an error preparing or executing the SQL statement.
|
* @throws DatabaseOperationException If there is an error preparing or executing the SQL statement.
|
||||||
*/
|
*/
|
||||||
public static function blacklistEntity(string $entity, string $operator, BlacklistType $type, ?int $expires = null): void
|
public static function blacklistEntity(string $entity, string $operator, BlacklistType $type, ?int $expires=null, ?string $evidence=null): string
|
||||||
{
|
{
|
||||||
if(empty($entity) || empty($operator))
|
if(empty($entity) || empty($operator))
|
||||||
{
|
{
|
||||||
|
@ -34,13 +38,22 @@
|
||||||
throw new InvalidArgumentException("Expiration time must be in the future or null for permanent blacklisting.");
|
throw new InvalidArgumentException("Expiration time must be in the future or null for permanent blacklisting.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!is_null($evidence) && !Validate::uuid($evidence))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException("Evidence must be a valid UUID.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$uuid = UuidV4::v4()->toRfc4122();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$stmt = DatabaseConnection::getConnection()->prepare("INSERT INTO blacklist (entity, operator, type, expires) VALUES (:entity, :operator, :type, :expires)");
|
$stmt = DatabaseConnection::getConnection()->prepare("INSERT INTO blacklist (uuid, entity, operator, type, expires, evidence) VALUES (:uuid, :entity, :operator, :type, :expires, :evidence)");
|
||||||
|
$stmt->bindParam(':uuid', $uuid);
|
||||||
$type = $type->value;
|
$type = $type->value;
|
||||||
$stmt->bindParam(':entity', $entity);
|
$stmt->bindParam(':entity', $entity);
|
||||||
$stmt->bindParam(':operator', $operator);
|
$stmt->bindParam(':operator', $operator);
|
||||||
$stmt->bindParam(':type', $type);
|
$stmt->bindParam(':type', $type);
|
||||||
|
$stmt->bindParam(':evidence', $evidence);
|
||||||
|
|
||||||
// Convert expires to datetime
|
// Convert expires to datetime
|
||||||
if(is_null($expires))
|
if(is_null($expires))
|
||||||
|
@ -56,6 +69,8 @@
|
||||||
{
|
{
|
||||||
throw new DatabaseOperationException("Failed to prepare SQL statement for blacklisting entity: " . $e->getMessage(), 0, $e);
|
throw new DatabaseOperationException("Failed to prepare SQL statement for blacklisting entity: " . $e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace FederationServer\Classes\Managers;
|
namespace FederationServer\Classes\Managers;
|
||||||
|
|
||||||
|
use FederationServer\Classes\Configuration;
|
||||||
use FederationServer\Classes\DatabaseConnection;
|
use FederationServer\Classes\DatabaseConnection;
|
||||||
use FederationServer\Classes\Utilities;
|
use FederationServer\Classes\Utilities;
|
||||||
use FederationServer\Exceptions\DatabaseOperationException;
|
use FederationServer\Exceptions\DatabaseOperationException;
|
||||||
|
@ -53,6 +54,77 @@
|
||||||
return $uuid;
|
return $uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the master operator with a predefined API key.
|
||||||
|
*
|
||||||
|
* @param string $apiKey The API key for the master operator.
|
||||||
|
* @return string The UUID of the created master operator.
|
||||||
|
* @throws DatabaseOperationException If there is an error during the database operation.
|
||||||
|
*/
|
||||||
|
private static function createMasterOperator(string $apiKey): string
|
||||||
|
{
|
||||||
|
if(empty($apiKey))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException('API key cannot be empty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strlen($apiKey) !== 32)
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException('API key must be exactly 32 characters long.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is used to create the master operator with a predefined API key.
|
||||||
|
// It should only be called once during the initial setup of the server.
|
||||||
|
$uuid = Uuid::v7()->toRfc4122();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$stmt = DatabaseConnection::getConnection()->prepare("INSERT INTO operators (uuid, api_key, name, manage_operators, manage_blacklist, is_client) VALUES (:uuid, :api_key, 'root', 1, 1, 1)");
|
||||||
|
$stmt->bindParam(':uuid', $uuid);
|
||||||
|
$stmt->bindParam(':api_key', $apiKey);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
catch (PDOException $e)
|
||||||
|
{
|
||||||
|
throw new DatabaseOperationException('Failed to create master operator', 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the master operator.
|
||||||
|
*
|
||||||
|
* This method checks if the master operator exists in the database.
|
||||||
|
* If it does not exist, it creates one with a predefined API key.
|
||||||
|
*
|
||||||
|
* @return OperatorRecord The master operator record.
|
||||||
|
* @throws DatabaseOperationException If there is an error during the database operation.
|
||||||
|
* @throws InvalidArgumentException If the API key for the master operator is not set in the configuration.
|
||||||
|
*/
|
||||||
|
public static function getMasterOperator(): OperatorRecord
|
||||||
|
{
|
||||||
|
// This method retrieves the master operator from the database.
|
||||||
|
// If the master operator does not exist, it creates one with a predefined API key.
|
||||||
|
$apiKey = Configuration::getServerConfiguration()->getApiKey();
|
||||||
|
|
||||||
|
if(empty($apiKey))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException('API key for master operator is not set in configuration.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$operator = self::getOperatorByApiKey($apiKey);
|
||||||
|
|
||||||
|
if($operator === null)
|
||||||
|
{
|
||||||
|
$uuid = self::createMasterOperator($apiKey);
|
||||||
|
$operator = self::getOperator($uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $operator;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an operator by their UUID.
|
* Retrieve an operator by their UUID.
|
||||||
*
|
*
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
use FederationServer\Interfaces\RequestHandlerInterface;
|
use FederationServer\Interfaces\RequestHandlerInterface;
|
||||||
use FederationServer\Interfaces\SerializableInterface;
|
use FederationServer\Interfaces\SerializableInterface;
|
||||||
use FederationServer\Objects\OperatorRecord;
|
use FederationServer\Objects\OperatorRecord;
|
||||||
|
use InvalidArgumentException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
abstract class RequestHandler implements RequestHandlerInterface
|
abstract class RequestHandler implements RequestHandlerInterface
|
||||||
|
@ -207,23 +208,52 @@
|
||||||
*/
|
*/
|
||||||
protected static function getAuthenticatedOperator(): ?OperatorRecord
|
protected static function getAuthenticatedOperator(): ?OperatorRecord
|
||||||
{
|
{
|
||||||
// First obtain the API key from the request headers or query parameters.
|
$apiKey = null;
|
||||||
$apiKey = $_SERVER['HTTP_API_KEY'] ?? $_GET['api_key'] ?? $_POST['api_key'] ?? null;
|
if (isset($_SERVER['HTTP_AUTHORIZATION']))
|
||||||
|
{
|
||||||
|
$authHeader = $_SERVER['HTTP_AUTHORIZATION'];
|
||||||
|
if (preg_match('/^Bearer\s+(\S+)$/', $authHeader, $matches))
|
||||||
|
{
|
||||||
|
$apiKey = $matches[1];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($apiKey))
|
if (empty($apiKey))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(strlen($apiKey) > 32)
|
if (strlen($apiKey) !== 32)
|
||||||
{
|
{
|
||||||
throw new RequestException('API key is too long', 400);
|
throw new RequestException('Invalid API key', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the given API key matches the master operator's API key, we can retrieve the master operator.
|
||||||
|
if(Configuration::getServerConfiguration()->getApiKey() !== null && $apiKey === Configuration::getServerConfiguration()->getApiKey())
|
||||||
|
{
|
||||||
|
// A master operator is automatically created if it does not exist.
|
||||||
|
// This is useful for initial setup or if the master operator was deleted.
|
||||||
|
// Master operators cannot be disabled, so we can safely return it.
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return OperatorManager::getMasterOperator();
|
||||||
|
}
|
||||||
|
catch (DatabaseOperationException $e)
|
||||||
|
{
|
||||||
|
throw new RequestException('Internal Database Error', 500, $e);
|
||||||
|
}
|
||||||
|
catch(InvalidArgumentException $e)
|
||||||
|
{
|
||||||
|
throw new RequestException('Invalid API Key Configuration', 500, $e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$operator = OperatorManager::getOperatorByApiKey($apiKey);
|
$operator = OperatorManager::getOperatorByApiKey($apiKey);
|
||||||
|
if ($operator === null)
|
||||||
if($operator === null)
|
|
||||||
{
|
{
|
||||||
throw new RequestException('Invalid API key', 401);
|
throw new RequestException('Invalid API key', 401);
|
||||||
}
|
}
|
||||||
|
@ -238,7 +268,6 @@
|
||||||
throw new RequestException('Operator is disabled', 403);
|
throw new RequestException('Operator is disabled', 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the operator is found and enabled, return the OperatorRecord object.
|
|
||||||
return $operator;
|
return $operator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
create table operators
|
create table operators
|
||||||
(
|
(
|
||||||
uuid varchar(36) default uuid() not null comment 'The Unique Universal Identifier for the operator'
|
uuid varchar(36) default uuid() not null comment 'The Unique Primary Index for the operator UUID'
|
||||||
primary key comment 'The Unique Primary Index for the operator UUID',
|
primary key,
|
||||||
|
name varchar(32) not null comment 'The public name of the operator',
|
||||||
api_key varchar(32) not null comment 'The current API key of the operator',
|
api_key varchar(32) not null comment 'The current API key of the operator',
|
||||||
manage_operators tinyint(1) default 0 not null comment 'Default: 0, 1=This operator can manage other operators by creating new ones, deleting existing ones or disabling existing ones, etc. 0=No such permissions are allowed',
|
manage_operators tinyint(1) default 0 not null comment 'Default: 0, 1=This operator can manage other operators by creating new ones, deleting existing ones or disabling existing ones, etc. 0=No such permissions are allowed',
|
||||||
manage_blacklist tinyint default 0 not null comment 'Default: 0, 1=This operator can manage the blacklist by adding/removing to the database, 0=No such permissions are allowed',
|
manage_blacklist tinyint default 0 not null comment 'Default: 0, 1=This operator can manage the blacklist by adding/removing to the database, 0=No such permissions are allowed',
|
||||||
is_client tinyint default 0 not null comment 'Default: 0, 1=This operator has access to client methods that allows the client to build the database of known entities and automatically report evidence or manage the database (if permitted to do so), 0=No such permissions are allowed',
|
is_client tinyint default 0 not null comment 'Default: 0, 1=This operator has access to client methods that allows the client to build the database of known entities and automatically report evidence or manage the database (if permitted to do so), 0=No such permissions are allowed',
|
||||||
|
disabled tinyint(1) default 0 not null comment 'Default: 0, 1=The operator is disabled, 0=The oprator is active',
|
||||||
created timestamp default current_timestamp() not null comment 'The Timestamp for when this operator record was created',
|
created timestamp default current_timestamp() not null comment 'The Timestamp for when this operator record was created',
|
||||||
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this operator record was last updated',
|
updated timestamp default current_timestamp() not null comment 'The Timestamp for when this operator record was last updated',
|
||||||
constraint operators_api_key_uindex
|
constraint operators_api_key_uindex
|
||||||
|
|
|
@ -117,16 +117,37 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* Get the currently authenticated operator.
|
||||||
|
*
|
||||||
|
* This method retrieves the currently authenticated operator, if any.
|
||||||
|
* If no operator is authenticated, it returns null.
|
||||||
|
*
|
||||||
|
* @param bool $requireAuthentication Whether to require authentication. Defaults to true.
|
||||||
|
* @return OperatorRecord|null The authenticated operator record or null if not authenticated.
|
||||||
|
* @throws RequestException If authentication is provided but is invalid/operator is disabled.
|
||||||
*/
|
*/
|
||||||
public static function getAuthenticatedOperator(bool $requireAuthentication=true): ?OperatorRecord
|
public static function getAuthenticatedOperator(bool $requireAuthentication=true): ?OperatorRecord
|
||||||
{
|
{
|
||||||
$authenticatedOperator = parent::getAuthenticatedOperator();
|
return parent::getAuthenticatedOperator();
|
||||||
if($requireAuthentication && $authenticatedOperator === null)
|
|
||||||
{
|
|
||||||
throw new RequestException('Unauthorized: No authenticated operator found', 401);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $authenticatedOperator;
|
/**
|
||||||
|
* Get the authenticated operator, throwing an exception if not authenticated.
|
||||||
|
*
|
||||||
|
* This method retrieves the currently authenticated operator. If no operator is authenticated,
|
||||||
|
* it throws a RequestException with a 401 Unauthorized status code.
|
||||||
|
*
|
||||||
|
* @return OperatorRecord The authenticated operator record.
|
||||||
|
* @throws RequestException If no operator is authenticated.
|
||||||
|
*/
|
||||||
|
public static function requireAuthenticatedOperator(): OperatorRecord
|
||||||
|
{
|
||||||
|
$operator = self::getAuthenticatedOperator();
|
||||||
|
if ($operator === null)
|
||||||
|
{
|
||||||
|
throw new RequestException('Authentication required', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $operator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue