733 lines
22 KiB
PHP
733 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Module-API für PrestaShop-Modul-Kompatibilität
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\Core;
|
|
|
|
use Doctrine\DBAL\DriverManager;
|
|
use Doctrine\DBAL\Exception;
|
|
|
|
class ModuleAPI
|
|
{
|
|
private static $instance = null;
|
|
private $moduleManager;
|
|
private $eventDispatcher;
|
|
private $cache;
|
|
private $logger;
|
|
private $enabled = true;
|
|
private $rateLimit = 1000; // Requests pro Stunde
|
|
private $rateLimitWindow = 3600; // 1 Stunde
|
|
private $apiKeys = [];
|
|
|
|
private function __construct()
|
|
{
|
|
$this->moduleManager = ModuleManager::getInstance();
|
|
$this->eventDispatcher = EventDispatcher::getInstance();
|
|
$this->cache = Cache::getInstance();
|
|
$this->logger = Logger::getInstance();
|
|
$this->loadApiKeys();
|
|
}
|
|
|
|
/**
|
|
* Singleton-Instanz abrufen
|
|
*/
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* API-Request verarbeiten
|
|
*/
|
|
public function handleRequest($method, $endpoint, $data = [], $headers = [])
|
|
{
|
|
if (!$this->enabled) {
|
|
return $this->createResponse(503, 'API deaktiviert');
|
|
}
|
|
|
|
// API-Key validieren
|
|
$apiKey = $this->extractApiKey($headers);
|
|
if (!$this->validateApiKey($apiKey)) {
|
|
return $this->createResponse(401, 'Ungültiger API-Key');
|
|
}
|
|
|
|
// Rate-Limiting prüfen
|
|
if (!$this->checkRateLimit($apiKey)) {
|
|
return $this->createResponse(429, 'Rate-Limit überschritten');
|
|
}
|
|
|
|
// Request loggen
|
|
$this->logger->info('API Request', [
|
|
'method' => $method,
|
|
'endpoint' => $endpoint,
|
|
'api_key' => $this->maskApiKey($apiKey),
|
|
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
|
]);
|
|
|
|
try {
|
|
// Endpoint auflösen
|
|
$handler = $this->resolveEndpoint($method, $endpoint);
|
|
|
|
if (!$handler) {
|
|
return $this->createResponse(404, 'Endpoint nicht gefunden');
|
|
}
|
|
|
|
// Request verarbeiten
|
|
$result = call_user_func($handler, $data, $headers);
|
|
|
|
// Response loggen
|
|
$this->logger->info('API Response', [
|
|
'method' => $method,
|
|
'endpoint' => $endpoint,
|
|
'status' => $result['status'] ?? 200
|
|
]);
|
|
|
|
return $result;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('API Error', [
|
|
'method' => $method,
|
|
'endpoint' => $endpoint,
|
|
'error' => $e->getMessage(),
|
|
'trace' => $e->getTraceAsString()
|
|
]);
|
|
|
|
return $this->createResponse(500, 'Interner Server-Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Module-Liste abrufen
|
|
*/
|
|
public function getModules($filters = [])
|
|
{
|
|
$cacheKey = 'api_modules_' . md5(serialize($filters));
|
|
|
|
// Cache prüfen
|
|
$cached = $this->cache->get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $this->createResponse(200, 'Module abgerufen', $cached);
|
|
}
|
|
|
|
$modules = $this->moduleManager->getAllModules();
|
|
|
|
// Filter anwenden
|
|
if (!empty($filters['active'])) {
|
|
$modules = array_filter($modules, function($module) {
|
|
return $module['active'] ?? false;
|
|
});
|
|
}
|
|
|
|
if (!empty($filters['type'])) {
|
|
$modules = array_filter($modules, function($module) use ($filters) {
|
|
return $module['type'] === $filters['type'];
|
|
});
|
|
}
|
|
|
|
if (!empty($filters['search'])) {
|
|
$search = strtolower($filters['search']);
|
|
$modules = array_filter($modules, function($module) use ($search) {
|
|
return strpos(strtolower($module['name']), $search) !== false ||
|
|
strpos(strtolower($module['description']), $search) !== false;
|
|
});
|
|
}
|
|
|
|
// Pagination
|
|
$page = $filters['page'] ?? 1;
|
|
$limit = min($filters['limit'] ?? 20, 100);
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
$total = count($modules);
|
|
$modules = array_slice($modules, $offset, $limit);
|
|
|
|
$result = [
|
|
'modules' => $modules,
|
|
'pagination' => [
|
|
'page' => $page,
|
|
'limit' => $limit,
|
|
'total' => $total,
|
|
'pages' => ceil($total / $limit)
|
|
]
|
|
];
|
|
|
|
// Cache setzen
|
|
$this->cache->set($cacheKey, $result, 300); // 5 Minuten
|
|
|
|
return $this->createResponse(200, 'Module abgerufen', $result);
|
|
}
|
|
|
|
/**
|
|
* Einzelnes Modul abrufen
|
|
*/
|
|
public function getModule($moduleName)
|
|
{
|
|
$cacheKey = 'api_module_' . $moduleName;
|
|
|
|
// Cache prüfen
|
|
$cached = $this->cache->get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $this->createResponse(200, 'Modul abgerufen', $cached);
|
|
}
|
|
|
|
$module = $this->moduleManager->getModule($moduleName);
|
|
|
|
if (!$module) {
|
|
return $this->createResponse(404, 'Modul nicht gefunden');
|
|
}
|
|
|
|
// Cache setzen
|
|
$this->cache->set($cacheKey, $module, 600); // 10 Minuten
|
|
|
|
return $this->createResponse(200, 'Modul abgerufen', $module);
|
|
}
|
|
|
|
/**
|
|
* Modul installieren
|
|
*/
|
|
public function installModule($moduleName, $data = [])
|
|
{
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('module.install.before', [
|
|
'module_name' => $moduleName,
|
|
'data' => $data
|
|
]);
|
|
|
|
try {
|
|
$result = $this->moduleManager->installModule($moduleName, $data);
|
|
|
|
if ($result) {
|
|
// Cache invalidieren
|
|
$this->cache->delete('api_modules_*');
|
|
$this->cache->delete('api_module_' . $moduleName);
|
|
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('module.install.after', [
|
|
'module_name' => $moduleName,
|
|
'result' => $result
|
|
]);
|
|
|
|
$this->logger->info('Module installiert via API', [
|
|
'module_name' => $moduleName,
|
|
'data' => $data
|
|
]);
|
|
|
|
return $this->createResponse(201, 'Modul erfolgreich installiert', $result);
|
|
} else {
|
|
return $this->createResponse(400, 'Modul-Installation fehlgeschlagen');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Module-Installation Fehler', [
|
|
'module_name' => $moduleName,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return $this->createResponse(500, 'Installation-Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modul deinstallieren
|
|
*/
|
|
public function uninstallModule($moduleName)
|
|
{
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('module.uninstall.before', [
|
|
'module_name' => $moduleName
|
|
]);
|
|
|
|
try {
|
|
$result = $this->moduleManager->uninstallModule($moduleName);
|
|
|
|
if ($result) {
|
|
// Cache invalidieren
|
|
$this->cache->delete('api_modules_*');
|
|
$this->cache->delete('api_module_' . $moduleName);
|
|
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('module.uninstall.after', [
|
|
'module_name' => $moduleName,
|
|
'result' => $result
|
|
]);
|
|
|
|
$this->logger->info('Module deinstalliert via API', [
|
|
'module_name' => $moduleName
|
|
]);
|
|
|
|
return $this->createResponse(200, 'Modul erfolgreich deinstalliert');
|
|
} else {
|
|
return $this->createResponse(400, 'Modul-Deinstallation fehlgeschlagen');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Module-Deinstallation Fehler', [
|
|
'module_name' => $moduleName,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return $this->createResponse(500, 'Deinstallation-Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modul aktivieren
|
|
*/
|
|
public function enableModule($moduleName)
|
|
{
|
|
try {
|
|
$result = $this->moduleManager->enableModule($moduleName);
|
|
|
|
if ($result) {
|
|
// Cache invalidieren
|
|
$this->cache->delete('api_modules_*');
|
|
$this->cache->delete('api_module_' . $moduleName);
|
|
|
|
$this->logger->info('Module aktiviert via API', [
|
|
'module_name' => $moduleName
|
|
]);
|
|
|
|
return $this->createResponse(200, 'Modul erfolgreich aktiviert');
|
|
} else {
|
|
return $this->createResponse(400, 'Modul-Aktivierung fehlgeschlagen');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Module-Aktivierung Fehler', [
|
|
'module_name' => $moduleName,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return $this->createResponse(500, 'Aktivierungs-Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modul deaktivieren
|
|
*/
|
|
public function disableModule($moduleName)
|
|
{
|
|
try {
|
|
$result = $this->moduleManager->disableModule($moduleName);
|
|
|
|
if ($result) {
|
|
// Cache invalidieren
|
|
$this->cache->delete('api_modules_*');
|
|
$this->cache->delete('api_module_' . $moduleName);
|
|
|
|
$this->logger->info('Module deaktiviert via API', [
|
|
'module_name' => $moduleName
|
|
]);
|
|
|
|
return $this->createResponse(200, 'Modul erfolgreich deaktiviert');
|
|
} else {
|
|
return $this->createResponse(400, 'Modul-Deaktivierung fehlgeschlagen');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Module-Deaktivierung Fehler', [
|
|
'module_name' => $moduleName,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return $this->createResponse(500, 'Deaktivierungs-Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modul-Konfiguration abrufen
|
|
*/
|
|
public function getModuleConfig($moduleName)
|
|
{
|
|
$cacheKey = 'api_module_config_' . $moduleName;
|
|
|
|
// Cache prüfen
|
|
$cached = $this->cache->get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $this->createResponse(200, 'Modul-Konfiguration abgerufen', $cached);
|
|
}
|
|
|
|
$config = $this->moduleManager->getModuleConfig($moduleName);
|
|
|
|
if (!$config) {
|
|
return $this->createResponse(404, 'Modul-Konfiguration nicht gefunden');
|
|
}
|
|
|
|
// Cache setzen
|
|
$this->cache->set($cacheKey, $config, 300); // 5 Minuten
|
|
|
|
return $this->createResponse(200, 'Modul-Konfiguration abgerufen', $config);
|
|
}
|
|
|
|
/**
|
|
* Modul-Konfiguration aktualisieren
|
|
*/
|
|
public function updateModuleConfig($moduleName, $config)
|
|
{
|
|
try {
|
|
$result = $this->moduleManager->updateModuleConfig($moduleName, $config);
|
|
|
|
if ($result) {
|
|
// Cache invalidieren
|
|
$this->cache->delete('api_module_config_' . $moduleName);
|
|
|
|
$this->logger->info('Module-Konfiguration aktualisiert via API', [
|
|
'module_name' => $moduleName,
|
|
'config' => $config
|
|
]);
|
|
|
|
return $this->createResponse(200, 'Modul-Konfiguration erfolgreich aktualisiert');
|
|
} else {
|
|
return $this->createResponse(400, 'Konfigurations-Update fehlgeschlagen');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Module-Konfigurations-Update Fehler', [
|
|
'module_name' => $moduleName,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return $this->createResponse(500, 'Update-Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Module-Statistiken abrufen
|
|
*/
|
|
public function getModuleStatistics()
|
|
{
|
|
$cacheKey = 'api_module_statistics';
|
|
|
|
// Cache prüfen
|
|
$cached = $this->cache->get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $this->createResponse(200, 'Module-Statistiken abgerufen', $cached);
|
|
}
|
|
|
|
$statistics = $this->moduleManager->getStatistics();
|
|
|
|
// Cache setzen
|
|
$this->cache->set($cacheKey, $statistics, 600); // 10 Minuten
|
|
|
|
return $this->createResponse(200, 'Module-Statistiken abgerufen', $statistics);
|
|
}
|
|
|
|
/**
|
|
* API-Status abrufen
|
|
*/
|
|
public function getApiStatus()
|
|
{
|
|
$status = [
|
|
'enabled' => $this->enabled,
|
|
'version' => '1.0.0',
|
|
'rate_limit' => $this->rateLimit,
|
|
'rate_limit_window' => $this->rateLimitWindow,
|
|
'total_modules' => count($this->moduleManager->getAllModules()),
|
|
'active_modules' => count(array_filter($this->moduleManager->getAllModules(), function($m) {
|
|
return $m['active'] ?? false;
|
|
})),
|
|
'uptime' => time() - strtotime('today'),
|
|
'memory_usage' => memory_get_usage(true),
|
|
'memory_peak' => memory_get_peak_usage(true)
|
|
];
|
|
|
|
return $this->createResponse(200, 'API-Status abgerufen', $status);
|
|
}
|
|
|
|
/**
|
|
* Endpoint auflösen
|
|
*/
|
|
private function resolveEndpoint($method, $endpoint)
|
|
{
|
|
$endpoints = [
|
|
'GET' => [
|
|
'/api/v1/modules' => [$this, 'getModules'],
|
|
'/api/v1/modules/{name}' => [$this, 'getModule'],
|
|
'/api/v1/modules/{name}/config' => [$this, 'getModuleConfig'],
|
|
'/api/v1/statistics' => [$this, 'getModuleStatistics'],
|
|
'/api/v1/status' => [$this, 'getApiStatus']
|
|
],
|
|
'POST' => [
|
|
'/api/v1/modules' => [$this, 'installModule'],
|
|
'/api/v1/modules/{name}/enable' => [$this, 'enableModule'],
|
|
'/api/v1/modules/{name}/disable' => [$this, 'disableModule'],
|
|
'/api/v1/modules/{name}/config' => [$this, 'updateModuleConfig']
|
|
],
|
|
'DELETE' => [
|
|
'/api/v1/modules/{name}' => [$this, 'uninstallModule']
|
|
]
|
|
];
|
|
|
|
if (!isset($endpoints[$method])) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($endpoints[$method] as $pattern => $handler) {
|
|
if ($this->matchPattern($pattern, $endpoint)) {
|
|
return $handler;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Pattern-Matching für Endpoints
|
|
*/
|
|
private function matchPattern($pattern, $endpoint)
|
|
{
|
|
$pattern = preg_replace('/\{([^}]+)\}/', '([^/]+)', $pattern);
|
|
return preg_match('#^' . $pattern . '$#', $endpoint);
|
|
}
|
|
|
|
/**
|
|
* API-Key aus Headers extrahieren
|
|
*/
|
|
private function extractApiKey($headers)
|
|
{
|
|
$apiKey = null;
|
|
|
|
// Verschiedene Header-Namen prüfen
|
|
$headerNames = ['X-API-Key', 'Authorization', 'Api-Key'];
|
|
|
|
foreach ($headerNames as $headerName) {
|
|
if (isset($headers[$headerName])) {
|
|
$value = $headers[$headerName];
|
|
|
|
// Bearer Token Format
|
|
if (strpos($value, 'Bearer ') === 0) {
|
|
$apiKey = substr($value, 7);
|
|
} else {
|
|
$apiKey = $value;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $apiKey;
|
|
}
|
|
|
|
/**
|
|
* API-Key validieren
|
|
*/
|
|
private function validateApiKey($apiKey)
|
|
{
|
|
if (!$apiKey) {
|
|
return false;
|
|
}
|
|
|
|
return isset($this->apiKeys[$apiKey]) && $this->apiKeys[$apiKey]['active'];
|
|
}
|
|
|
|
/**
|
|
* Rate-Limiting prüfen
|
|
*/
|
|
private function checkRateLimit($apiKey)
|
|
{
|
|
$cacheKey = 'api_rate_limit_' . md5($apiKey);
|
|
$current = time();
|
|
|
|
$requests = $this->cache->get($cacheKey, []);
|
|
|
|
// Alte Requests entfernen
|
|
$requests = array_filter($requests, function($timestamp) use ($current) {
|
|
return $timestamp > ($current - $this->rateLimitWindow);
|
|
});
|
|
|
|
// Neuen Request hinzufügen
|
|
$requests[] = $current;
|
|
|
|
// Cache aktualisieren
|
|
$this->cache->set($cacheKey, $requests, $this->rateLimitWindow);
|
|
|
|
return count($requests) <= $this->rateLimit;
|
|
}
|
|
|
|
/**
|
|
* API-Keys laden
|
|
*/
|
|
private function loadApiKeys()
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
SELECT api_key, name, permissions, active, created_at
|
|
FROM ws_api_keys
|
|
WHERE active = 1
|
|
');
|
|
$stmt->execute();
|
|
|
|
$keys = $stmt->fetchAllAssociative();
|
|
|
|
foreach ($keys as $key) {
|
|
$this->apiKeys[$key['api_key']] = [
|
|
'name' => $key['name'],
|
|
'permissions' => json_decode($key['permissions'], true) ?: [],
|
|
'active' => (bool)$key['active'],
|
|
'created_at' => $key['created_at']
|
|
];
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('API-Keys laden Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API-Key maskieren für Logs
|
|
*/
|
|
private function maskApiKey($apiKey)
|
|
{
|
|
if (strlen($apiKey) <= 8) {
|
|
return str_repeat('*', strlen($apiKey));
|
|
}
|
|
|
|
return substr($apiKey, 0, 4) . str_repeat('*', strlen($apiKey) - 8) . substr($apiKey, -4);
|
|
}
|
|
|
|
/**
|
|
* Response erstellen
|
|
*/
|
|
private function createResponse($status, $message, $data = null)
|
|
{
|
|
$response = [
|
|
'status' => $status,
|
|
'message' => $message,
|
|
'timestamp' => date('c'),
|
|
'request_id' => uniqid('api_', true)
|
|
];
|
|
|
|
if ($data !== null) {
|
|
$response['data'] = $data;
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* API aktivieren/deaktivieren
|
|
*/
|
|
public function setEnabled($enabled)
|
|
{
|
|
$this->enabled = $enabled;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* API-Status prüfen
|
|
*/
|
|
public function isEnabled()
|
|
{
|
|
return $this->enabled;
|
|
}
|
|
|
|
/**
|
|
* Rate-Limit setzen
|
|
*/
|
|
public function setRateLimit($limit, $window = 3600)
|
|
{
|
|
$this->rateLimit = $limit;
|
|
$this->rateLimitWindow = $window;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* API-Key erstellen
|
|
*/
|
|
public function createApiKey($name, $permissions = [])
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$apiKey = $this->generateApiKey();
|
|
|
|
$stmt = $conn->prepare('
|
|
INSERT INTO ws_api_keys (
|
|
api_key, name, permissions, active, created_at
|
|
) VALUES (?, ?, ?, 1, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$apiKey,
|
|
$name,
|
|
json_encode($permissions)
|
|
]);
|
|
|
|
// API-Keys neu laden
|
|
$this->loadApiKeys();
|
|
|
|
$this->logger->info('API-Key erstellt', [
|
|
'name' => $name,
|
|
'permissions' => $permissions
|
|
]);
|
|
|
|
return $apiKey;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('API-Key erstellen Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API-Key löschen
|
|
*/
|
|
public function deleteApiKey($apiKey)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
DELETE FROM ws_api_keys
|
|
WHERE api_key = ?
|
|
');
|
|
|
|
$stmt->execute([$apiKey]);
|
|
|
|
// API-Keys neu laden
|
|
$this->loadApiKeys();
|
|
|
|
$this->logger->info('API-Key gelöscht', [
|
|
'api_key' => $this->maskApiKey($apiKey)
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('API-Key löschen Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API-Key generieren
|
|
*/
|
|
private function generateApiKey()
|
|
{
|
|
return 'ws_' . bin2hex(random_bytes(32));
|
|
}
|
|
}
|