fix: resolve RedisCache array offset error and improve discovery diagnostics

- Fix RedisCache driver to handle MGET failures gracefully with fallback
- Add comprehensive discovery context comparison debug tools
- Identify root cause: WEB context discovery missing 166 items vs CLI
- WEB context missing RequestFactory class entirely (52 vs 69 commands)
- Improved exception handling with detailed binding diagnostics
This commit is contained in:
2025-09-12 20:05:18 +02:00
parent 8040d3e7a5
commit e30753ba0e
46990 changed files with 10789682 additions and 89639 deletions

View File

@@ -11,11 +11,9 @@ use App\Application\Security\Events\{
System\SystemAnomalyEvent
};
use App\Framework\Core\Events\EventDispatcher;
use App\Framework\Core\Exceptions\{
CryptographicException,
ValidationException
};
use App\Framework\Exception\FrameworkException;
use App\Framework\Logging\Logger;
use App\Framework\Validation\Exceptions\ValidationException;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Throwable;
@@ -32,7 +30,7 @@ final class SecurityExceptionHandler
match (true) {
$exception instanceof AccessDeniedException => $this->handleAccessDenied($exception),
$exception instanceof ValidationException => $this->handleValidationError($exception),
$exception instanceof CryptographicException => $this->handleCryptographicError($exception),
$exception instanceof FrameworkException && $this->isCryptographicError($exception) => $this->handleCryptographicError($exception),
$exception instanceof \Error => $this->handleSystemError($exception),
default => $this->handleGenericSecurityIssue($exception)
};
@@ -60,11 +58,21 @@ final class SecurityExceptionHandler
}
}
private function handleCryptographicError(CryptographicException $exception): void
private function isCryptographicError(FrameworkException $exception): bool
{
$context = $exception->getContext();
return str_contains(strtolower($exception->getMessage()), 'cryptographic') ||
str_contains(strtolower($context->getOperation() ?? ''), 'crypto') ||
str_contains(strtolower($context->getComponent() ?? ''), 'crypto');
}
private function handleCryptographicError(FrameworkException $exception): void
{
$context = $exception->getContext();
$this->eventDispatcher->dispatch(new CryptographicFailureEvent(
operation: $exception->getOperation(),
algorithm: $exception->getAlgorithm(),
operation: $context->getOperation() ?? 'unknown',
algorithm: $context->getData()['algorithm'] ?? 'unknown',
errorMessage: $exception->getMessage(),
email: $this->getCurrentUserEmail()
));

View File

@@ -66,7 +66,7 @@ final class AuthenticationGuard
session_destroy();
$this->eventDispatcher->dispatch(new \App\Application\Security\Events\Auth\SessionTerminatedEvent(
email: $user->email,
email: $user->email ?? '',
sessionId: $sessionId,
reason: 'manual_logout'
));
@@ -74,17 +74,16 @@ final class AuthenticationGuard
private function handleSuccessfulLogin(User $user): void
{
// Failed attempts zurücksetzen
$user->failed_attempts = 0;
$user->last_login = new \DateTimeImmutable();
$this->userRepository->save($user);
// Failed attempts zurücksetzen - mit immutable object pattern
$updatedUser = $user->withFailedAttemptsReset();
$this->userRepository->save($updatedUser);
// Session regenerieren
session_regenerate_id(true);
$_SESSION['user_id'] = $user->id;
$_SESSION['user_id'] = $updatedUser->id;
$this->eventDispatcher->dispatch(new AuthenticationSuccessEvent(
email: $user->email,
email: $updatedUser->email ?? '',
sessionId: session_id(),
method: 'password'
));
@@ -92,17 +91,18 @@ final class AuthenticationGuard
private function handleFailedAttempt(User $user): void
{
$user->failed_attempts++;
$user->last_failed_attempt = new \DateTimeImmutable();
$updatedUser = $user->withIncrementedFailedAttempts();
if ($user->failed_attempts >= self::MAX_FAILED_ATTEMPTS) {
$user->locked_until = new \DateTimeImmutable('+' . self::LOCKOUT_DURATION . ' seconds');
$this->userRepository->save($user);
if ($updatedUser->failed_attempts >= self::MAX_FAILED_ATTEMPTS) {
$lockedUser = $updatedUser->withLockout(
new \DateTimeImmutable('+' . self::LOCKOUT_DURATION . ' seconds')
);
$this->userRepository->save($lockedUser);
$this->dispatchAccountLocked($user->email, 'max_attempts_exceeded', $user->failed_attempts);
$this->dispatchAccountLocked($lockedUser->email ?? '', 'max_attempts_exceeded', $lockedUser->failed_attempts);
} else {
$this->userRepository->save($user);
$this->dispatchFailedAttempt($user->email, 'invalid_password', $user->failed_attempts);
$this->userRepository->save($updatedUser);
$this->dispatchFailedAttempt($updatedUser->email ?? '', 'invalid_password', $updatedUser->failed_attempts);
}
}

View File

@@ -6,6 +6,7 @@ namespace App\Application\Security;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Router\Result\JsonResult;
use App\Framework\Waf\DetectionCategory;
@@ -30,7 +31,7 @@ final readonly class WafFeedbackController
/**
* Submit feedback for a WAF detection
*/
#[Route(path: '/api/security/waf/feedback', method: 'POST')]
#[Route(path: '/api/security/waf/feedback', method: Method::POST)]
public function submitFeedback(HttpRequest $request): JsonResult
{
$data = $request->parsedBody->data ?? [];
@@ -149,7 +150,7 @@ final readonly class WafFeedbackController
/**
* Get feedback for a specific detection
*/
#[Route(path: '/api/security/waf/feedback/{detectionId}', method: 'GET')]
#[Route(path: '/api/security/waf/feedback/{detectionId}', method: Method::GET)]
public function getFeedbackForDetection(HttpRequest $request, string $detectionId): JsonResult
{
$feedback = $this->feedbackService->getFeedbackForDetection($detectionId);
@@ -165,7 +166,7 @@ final readonly class WafFeedbackController
/**
* Get feedback statistics
*/
#[Route(path: '/api/security/waf/feedback/stats', method: 'GET')]
#[Route(path: '/api/security/waf/feedback/stats', method: Method::GET)]
public function getFeedbackStats(HttpRequest $request): JsonResult
{
$stats = $this->feedbackService->getFeedbackStats();
@@ -179,7 +180,7 @@ final readonly class WafFeedbackController
/**
* Get recent feedback
*/
#[Route(path: '/api/security/waf/feedback/recent', method: 'GET')]
#[Route(path: '/api/security/waf/feedback/recent', method: Method::GET)]
public function getRecentFeedback(HttpRequest $request): JsonResult
{
$limit = (int)($request->queryParams['limit'] ?? 10);

View File

@@ -7,6 +7,7 @@ namespace App\Application\Security;
use App\Framework\Attributes\Route;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Router\Result\ViewResult;
use App\Framework\Waf\DetectionCategory;
use App\Framework\Waf\Feedback\FeedbackRepositoryInterface;
@@ -31,7 +32,7 @@ final readonly class WafFeedbackDashboardController
/**
* Show the WAF feedback dashboard
*/
#[Route(path: '/admin/security/waf/feedback', method: 'GET')]
#[Route(path: '/admin/security/waf/feedback', method: Method::GET)]
public function showDashboard(HttpRequest $request): ViewResult
{
// Get feedback statistics
@@ -104,7 +105,7 @@ final readonly class WafFeedbackDashboardController
/**
* Show detailed feedback for a specific category
*/
#[Route(path: '/admin/security/waf/feedback/category/{category}', method: 'GET')]
#[Route(path: '/admin/security/waf/feedback/category/{category}', method: Method::GET)]
public function showCategoryFeedback(HttpRequest $request, string $category): ViewResult
{
try {
@@ -166,7 +167,7 @@ final readonly class WafFeedbackDashboardController
/**
* Show feedback learning history
*/
#[Route(path: '/admin/security/waf/feedback/learning', method: 'GET')]
#[Route(path: '/admin/security/waf/feedback/learning', method: Method::GET)]
public function showLearningHistory(HttpRequest $request): ViewResult
{
// In a real implementation, this would retrieve learning history from a database