diff --git a/src/FederationServer/Classes/Enums/Method.php b/src/FederationServer/Classes/Enums/Method.php index eddc3b0..099741c 100644 --- a/src/FederationServer/Classes/Enums/Method.php +++ b/src/FederationServer/Classes/Enums/Method.php @@ -11,14 +11,19 @@ use FederationServer\Methods\Entities\DeleteEntity; use FederationServer\Methods\Entities\GetEntity; use FederationServer\Methods\Entities\ListEntities; + use FederationServer\Methods\Entities\ListEntityAuditLogs; + use FederationServer\Methods\Entities\ListEntityEvidence; use FederationServer\Methods\Entities\PushEntity; use FederationServer\Methods\Entities\QueryEntity; + use FederationServer\Methods\Evidence\ListEvidence; use FederationServer\Methods\Operators\CreateOperator; use FederationServer\Methods\Operators\DeleteOperator; use FederationServer\Methods\Operators\DisableOperator; use FederationServer\Methods\Operators\EnableOperator; use FederationServer\Methods\Operators\GetOperator; use FederationServer\Methods\Operators\GetSelfOperator; + use FederationServer\Methods\Operators\ListOperatorAuditLogs; + use FederationServer\Methods\Operators\ListOperatorEvidence; use FederationServer\Methods\Operators\ListOperators; use FederationServer\Methods\Operators\ManageBlacklistPermission; use FederationServer\Methods\Operators\ManageClientPermission; @@ -41,12 +46,16 @@ case MANAGE_OPERATORS_PERMISSION; case MANAGE_BLACKLIST_PERMISSION; case MANAGE_CLIENT_PERMISSION; + case LIST_OPERATOR_EVIDENCE; + case LIST_OPERATOR_AUDIT_LOGS; case GET_ENTITY; case DELETE_ENTITY; case LIST_ENTITIES; case QUERY_ENTITY; case PUSH_ENTITY; + case LIST_ENTITY_EVIDENCE; + case LIST_ENTITY_AUDIT_LOGS; case LIST_EVIDENCE; case ADD_EVIDENCE; @@ -106,6 +115,12 @@ case self::PUSH_ENTITY: PushEntity::handleRequest(); break; + case self::LIST_ENTITY_EVIDENCE: + ListEntityEvidence::handleRequest(); + break; + case self::LIST_ENTITY_AUDIT_LOGS: + ListEntityAuditLogs::handleRequest(); + break; case self::LIST_OPERATORS: ListOperators::handleRequest(); @@ -140,9 +155,15 @@ case self::MANAGE_CLIENT_PERMISSION: ManageClientPermission::handleRequest(); break; + case self::LIST_OPERATOR_EVIDENCE: + ListOperatorEvidence::handleRequest(); + break; + case self::LIST_OPERATOR_AUDIT_LOGS: + ListOperatorAuditLogs::handleRequest(); + break; case self::LIST_EVIDENCE: - throw new \Exception('To be implemented'); + ListEvidence::handleRequest(); break; case self::ADD_EVIDENCE: throw new \Exception('To be implemented'); @@ -197,6 +218,8 @@ $path === '/entities/query' && $requestMethod === 'POST' => Method::QUERY_ENTITY, preg_match('#^/entities/([a-fA-F0-9\-]{36,})$#', $path) && $requestMethod === 'GET' => Method::GET_ENTITY, preg_match('#^/entities/([a-fA-F0-9\-]{36,})$#', $path) && $requestMethod === 'DELETE' => Method::DELETE_ENTITY, + preg_match('#^/entities/([a-fA-F0-9\-]{36,})/evidence$#', $path) && $requestMethod === 'GET' => Method::LIST_ENTITY_EVIDENCE, + preg_match('#^/entities/([a-fA-F0-9\-]{36,})/audit$#', $path) && $requestMethod === 'GET' => Method::LIST_ENTITY_AUDIT_LOGS, $path === '/blacklist' && $requestMethod === 'GET' => Method::LIST_BLACKLIST, $path === '/blacklist' && $requestMethod === 'POST' => Method::CREATE_BLACKLIST, @@ -221,6 +244,8 @@ preg_match('#^/operators/([a-fA-F0-9\-]{36,})/manage_operators$#', $path) && $requestMethod === 'POST' => Method::MANAGE_OPERATORS_PERMISSION, preg_match('#^/operators/([a-fA-F0-9\-]{36,})/manage_blacklist$#', $path) && $requestMethod === 'POST' => Method::MANAGE_BLACKLIST_PERMISSION, preg_match('#^/operators/([a-fA-F0-9\-]{36,})/manage_client$#', $path) && $requestMethod === 'POST' => Method::MANAGE_CLIENT_PERMISSION, + preg_match('#^/operators/([a-fA-F0-9\-]{36,})/evidence$#', $path) && $requestMethod === 'GET' => Method::LIST_OPERATOR_EVIDENCE, + preg_match('#^/operators/([a-fA-F0-9\-]{36,})/audit$#', $path) && $requestMethod === 'GET' => Method::LIST_OPERATOR_AUDIT_LOGS, default => null, }; diff --git a/src/FederationServer/Classes/Managers/OperatorManager.php b/src/FederationServer/Classes/Managers/OperatorManager.php index 5da279b..d17e9e5 100644 --- a/src/FederationServer/Classes/Managers/OperatorManager.php +++ b/src/FederationServer/Classes/Managers/OperatorManager.php @@ -89,6 +89,27 @@ } } + public static function operatorExists(string $uuid): bool + { + if(empty($uuid)) + { + throw new InvalidArgumentException('Operator UUID cannot be empty.'); + } + + try + { + $stmt = DatabaseConnection::getConnection()->prepare("SELECT COUNT(*) FROM operators WHERE uuid=:uuid"); + $stmt->bindParam(':uuid', $uuid); + $stmt->execute(); + + return $stmt->fetchColumn() > 0; // Returns true if operator exists, false otherwise + } + catch (PDOException $e) + { + throw new DatabaseOperationException(sprintf("Failed to check existence of operator with UUID '%s'", $uuid), 0, $e); + } + } + /** * Retrieve an operator by their API key. * diff --git a/src/FederationServer/Methods/Entities/ListEntityAuditLogs.php b/src/FederationServer/Methods/Entities/ListEntityAuditLogs.php new file mode 100644 index 0000000..00aa2b0 --- /dev/null +++ b/src/FederationServer/Methods/Entities/ListEntityAuditLogs.php @@ -0,0 +1,99 @@ +isPublicAuditLogs() && $authenticatedOperator === null) + { + throw new RequestException('Unauthorized: Public audit logs are disabled and no operator is authenticated', 403); + } + + if(!preg_match('#^/entities/([a-fA-F0-9\-]{36,})/audit$#', FederationServer::getPath(), $matches)) + { + throw new RequestException('Bad Request: Entity UUID is required', 400); + } + + $entityUuid = $matches[1]; + if(!$entityUuid) + { + throw new RequestException('Bad Request: Entity UUID is required', 400); + } + + $limit = (int) (FederationServer::getParameter('limit') ?? Configuration::getServerConfiguration()->getListAuditLogsMaxItems()); + $page = (int) (FederationServer::getParameter('page') ?? 1); + + if($limit < 1 || $limit > Configuration::getServerConfiguration()->getListAuditLogsMaxItems()) + { + $limit = Configuration::getServerConfiguration()->getListAuditLogsMaxItems(); + } + + if($page < 1) + { + $page = 1; + } + + $results = []; + + if($authenticatedOperator === null) + { + // Public audit logs are enabled, filter by public entries + $filteredEntries = Configuration::getServerConfiguration()->getPublicAuditEntries(); + } + else + { + // If an operator is authenticated, we can retrieve all entries + $filteredEntries = null; + } + + try + { + if(!EntitiesManager::entityExistsByUuid($entityUuid)) + { + throw new RequestException('Not Found: Entity with the specified UUID does not exist', 404); + } + + $auditLogs = AuditLogManager::getEntriesByEntity($entityUuid, $limit, $page, $filteredEntries); + foreach($auditLogs as $logRecord) + { + $operatorRecord = null; + $entityRecord = null; + + if($logRecord->getOperator() !== null) + { + $operatorRecord = OperatorManager::getOperator($logRecord->getOperator()); + } + + if($logRecord->getEntity() !== null) + { + $entityRecord = EntitiesManager::getEntityByUuid($logRecord->getEntity()); + } + + $results[] = new PublicAuditRecord($logRecord, $operatorRecord, $entityRecord); + } + } + catch (DatabaseOperationException $e) + { + throw new RequestException('Internal Server Error: Unable to retrieve audit logs', 500, $e); + } + + self::successResponse(array_map(fn($log) => $log->toArray(), $results)); + } + } + diff --git a/src/FederationServer/Methods/Entities/ListEntityEvidence.php b/src/FederationServer/Methods/Entities/ListEntityEvidence.php new file mode 100644 index 0000000..f7a3428 --- /dev/null +++ b/src/FederationServer/Methods/Entities/ListEntityEvidence.php @@ -0,0 +1,77 @@ +isPublicEvidence() && $authenticatedOperator === null) + { + throw new RequestException('Unauthorized: You must be authenticated to list evidence', 401); + } + + if($authenticatedOperator !== null) + { + $includeConfidential = true; + } + + $limit = (int) (FederationServer::getParameter('limit') ?? Configuration::getServerConfiguration()->getListEvidenceMaxItems()); + $page = (int) (FederationServer::getParameter('page') ?? 1); + + if($limit < 1 || $limit > Configuration::getServerConfiguration()->getListEvidenceMaxItems()) + { + $limit = Configuration::getServerConfiguration()->getListEvidenceMaxItems(); + } + + if($page < 1) + { + $page = 1; + } + + + if(!preg_match('#^/entities/([a-fA-F0-9\-]{36,})/evidence$#', FederationServer::getPath(), $matches)) + { + throw new RequestException('Bad Request: Entity UUID is required', 400); + } + + $entityUuid = $matches[1]; + if(!$entityUuid) + { + throw new RequestException('Bad Request: Entity UUID is required', 400); + } + + try + { + $existingEntity = EntitiesManager::getEntityByUuid($entityUuid); + if($existingEntity === null) + { + throw new RequestException('Entity Not Found', 404); + } + + $evidenceRecords = EvidenceManager::getEvidenceRecords($limit, $page, $includeConfidential); + } + catch (DatabaseOperationException $e) + { + throw new RequestException('Internal Server Error: Unable to retrieve evidence', 500, $e); + } + + $result = array_map(fn($evidence) => $evidence->toArray(), $evidenceRecords); + self::successResponse($result); + } + } + diff --git a/src/FederationServer/Methods/Evidence/ListEvidence.php b/src/FederationServer/Methods/Evidence/ListEvidence.php index 90a3653..638dd6e 100644 --- a/src/FederationServer/Methods/Evidence/ListEvidence.php +++ b/src/FederationServer/Methods/Evidence/ListEvidence.php @@ -18,11 +18,18 @@ public static function handleRequest(): void { $authenticatedOperator = FederationServer::getAuthenticatedOperator(false); + $includeConfidential = false; + if(!Configuration::getServerConfiguration()->isPublicEvidence() && $authenticatedOperator === null) { throw new RequestException('Unauthorized: You must be authenticated to list evidence', 401); } + if($authenticatedOperator !== null) + { + $includeConfidential = true; + } + $limit = (int) (FederationServer::getParameter('limit') ?? Configuration::getServerConfiguration()->getListEvidenceMaxItems()); $page = (int) (FederationServer::getParameter('page') ?? 1); @@ -38,7 +45,7 @@ try { - $evidenceRecords = EvidenceManager::getEvidenceRecords($limit, $page); + $evidenceRecords = EvidenceManager::getEvidenceRecords($limit, $page, $includeConfidential); } catch (DatabaseOperationException $e) { diff --git a/src/FederationServer/Methods/Operators/ListOperatorAuditLogs.php b/src/FederationServer/Methods/Operators/ListOperatorAuditLogs.php new file mode 100644 index 0000000..42c8e8f --- /dev/null +++ b/src/FederationServer/Methods/Operators/ListOperatorAuditLogs.php @@ -0,0 +1,99 @@ +isPublicAuditLogs() && $authenticatedOperator === null) + { + throw new RequestException('Unauthorized: Public audit logs are disabled and no operator is authenticated', 403); + } + + if(!preg_match('#^/operators/([a-fA-F0-9\-]{36,})/audit$#', FederationServer::getPath(), $matches)) + { + throw new RequestException('Bad Request: Operator UUID is required', 400); + } + + $operatorUuid = $matches[1]; + if(!$operatorUuid) + { + throw new RequestException('Bad Request: Operator UUID is required', 400); + } + + $limit = (int) (FederationServer::getParameter('limit') ?? Configuration::getServerConfiguration()->getListAuditLogsMaxItems()); + $page = (int) (FederationServer::getParameter('page') ?? 1); + + if($limit < 1 || $limit > Configuration::getServerConfiguration()->getListAuditLogsMaxItems()) + { + $limit = Configuration::getServerConfiguration()->getListAuditLogsMaxItems(); + } + + if($page < 1) + { + $page = 1; + } + + $results = []; + + if($authenticatedOperator === null) + { + // Public audit logs are enabled, filter by public entries + $filteredEntries = Configuration::getServerConfiguration()->getPublicAuditEntries(); + } + else + { + // If an operator is authenticated, we can retrieve all entries + $filteredEntries = null; + } + + try + { + if(!OperatorManager::operatorExists($operatorUuid)) + { + throw new RequestException('Not Found: Operator with the specified UUID does not exist', 404); + } + + $auditLogs = AuditLogManager::getEntriesByOperator($operatorUuid, $limit, $page, $filteredEntries); + foreach($auditLogs as $logRecord) + { + $operatorRecord = null; + $entityRecord = null; + + if($logRecord->getOperator() !== null) + { + $operatorRecord = OperatorManager::getOperator($logRecord->getOperator()); + } + + if($logRecord->getEntity() !== null) + { + $entityRecord = EntitiesManager::getEntityByUuid($logRecord->getEntity()); + } + + $results[] = new PublicAuditRecord($logRecord, $operatorRecord, $entityRecord); + } + } + catch (DatabaseOperationException $e) + { + throw new RequestException('Internal Server Error: Unable to retrieve audit logs', 500, $e); + } + + self::successResponse(array_map(fn($log) => $log->toArray(), $results)); + } + } + diff --git a/src/FederationServer/Methods/Operators/ListOperatorEvidence.php b/src/FederationServer/Methods/Operators/ListOperatorEvidence.php new file mode 100644 index 0000000..fca4de6 --- /dev/null +++ b/src/FederationServer/Methods/Operators/ListOperatorEvidence.php @@ -0,0 +1,76 @@ +isPublicEvidence() && $authenticatedOperator === null) + { + throw new RequestException('Unauthorized: You must be authenticated to list evidence', 401); + } + + if($authenticatedOperator !== null) + { + $listConfidential = true; + } + + $limit = (int) (FederationServer::getParameter('limit') ?? Configuration::getServerConfiguration()->getListEvidenceMaxItems()); + $page = (int) (FederationServer::getParameter('page') ?? 1); + + if($limit < 1 || $limit > Configuration::getServerConfiguration()->getListEvidenceMaxItems()) + { + $limit = Configuration::getServerConfiguration()->getListEvidenceMaxItems(); + } + + if($page < 1) + { + $page = 1; + } + + + if(!preg_match('#^/operators/([a-fA-F0-9\-]{36,})/evidence$#', FederationServer::getPath(), $matches)) + { + throw new RequestException('Bad Request: Operator UUID is required', 400); + } + + $operatorUuid = $matches[1]; + if(!$operatorUuid) + { + throw new RequestException('Bad Request: Operator UUID is required', 400); + } + + try + { + if(!OperatorManager::operatorExists($operatorUuid)) + { + throw new RequestException('Operator Not Found', 404); + } + + $evidenceRecords = EvidenceManager::getEvidenceByOperator($operatorUuid, $limit, $page, $listConfidential); + } + catch (DatabaseOperationException $e) + { + throw new RequestException('Internal Server Error: Unable to retrieve evidence', 500, $e); + } + + $result = array_map(fn($evidence) => $evidence->toArray(), $evidenceRecords); + self::successResponse($result); + } + } +