Files
michaelschiemer/src/Framework/Waf/Rules/RuleEvaluationResult.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

301 lines
8.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Waf\Rules;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Waf\Rules\ValueObjects\RuleMatch;
/**
* Result of rule engine evaluation
*/
final readonly class RuleEvaluationResult
{
public function __construct(
public array $matches,
public int $evaluatedRules,
public int $skippedRules,
public int $errorCount,
public array $errors,
public Duration $evaluationTime,
public Timestamp $timestamp
) {
}
/**
* Check if any rules matched
*/
public function hasMatches(): bool
{
return ! empty($this->matches);
}
/**
* Get number of matches
*/
public function getMatchCount(): int
{
return count($this->matches);
}
/**
* Get matches by severity
*/
public function getMatchesBySeverity(string $severity): array
{
return array_filter(
$this->matches,
fn (RuleMatch $match) => $match->severity->value === $severity
);
}
/**
* Get critical matches
*/
public function getCriticalMatches(): array
{
return $this->getMatchesBySeverity('critical');
}
/**
* Get high severity matches
*/
public function getHighSeverityMatches(): array
{
return $this->getMatchesBySeverity('high');
}
/**
* Get blocking matches (matches that should block the request)
*/
public function getBlockingMatches(): array
{
return array_filter(
$this->matches,
fn (RuleMatch $match) => $match->shouldBlock()
);
}
/**
* Get matches that should trigger alerts
*/
public function getAlertingMatches(): array
{
return array_filter(
$this->matches,
fn (RuleMatch $match) => $match->shouldAlert()
);
}
/**
* Get OWASP Top 10 matches
*/
public function getOwaspTop10Matches(): array
{
return array_filter(
$this->matches,
fn (RuleMatch $match) => $match->category->isOwaspTop10()
);
}
/**
* Get matches by category
*/
public function getMatchesByCategory(string $category): array
{
return array_filter(
$this->matches,
fn (RuleMatch $match) => $match->category->value === $category
);
}
/**
* Check if any blocking matches exist
*/
public function shouldBlock(): bool
{
return ! empty($this->getBlockingMatches());
}
/**
* Check if any alerting matches exist
*/
public function shouldAlert(): bool
{
return ! empty($this->getAlertingMatches());
}
/**
* Check if evaluation had errors
*/
public function hasErrors(): bool
{
return $this->errorCount > 0;
}
/**
* Get highest severity level from matches
*/
public function getHighestSeverity(): ?string
{
if (empty($this->matches)) {
return null;
}
$severityOrder = ['critical', 'high', 'medium', 'low', 'info'];
foreach ($severityOrder as $severity) {
if (! empty($this->getMatchesBySeverity($severity))) {
return $severity;
}
}
return null;
}
/**
* Get categories of all matches
*/
public function getMatchedCategories(): array
{
$categories = [];
foreach ($this->matches as $match) {
$categories[$match->category->value] = $match->category;
}
return array_values($categories);
}
/**
* Get rule IDs of all matches
*/
public function getMatchedRuleIds(): array
{
return array_map(
fn (RuleMatch $match) => $match->ruleId->value,
$this->matches
);
}
/**
* Calculate overall confidence score
*/
public function getOverallConfidence(): Percentage
{
if (empty($this->matches)) {
return Percentage::from(0.0);
}
$totalConfidence = 0.0;
$confidenceCount = 0;
foreach ($this->matches as $match) {
if ($match->confidence !== null) {
$totalConfidence += $match->confidence->getValue();
$confidenceCount++;
}
}
if ($confidenceCount === 0) {
return Percentage::from(0.0);
}
return Percentage::from($totalConfidence / $confidenceCount);
}
/**
* Calculate threat score based on matches
*/
public function getThreatScore(): Percentage
{
if (empty($this->matches)) {
return Percentage::from(0.0);
}
$score = 0.0;
$maxScore = 0.0;
foreach ($this->matches as $match) {
$matchScore = match ($match->severity->value) {
'critical' => 25.0,
'high' => 20.0,
'medium' => 10.0,
'low' => 5.0,
'info' => 1.0,
default => 0.0
};
// Apply confidence modifier
if ($match->confidence !== null) {
$matchScore *= ($match->confidence->getValue() / 100.0);
}
$score += $matchScore;
$maxScore = max($maxScore, $matchScore);
}
// Use weighted combination: 70% cumulative + 30% maximum
$finalScore = ($score * 0.7) + ($maxScore * 0.3);
return Percentage::from(min(100.0, $finalScore));
}
/**
* Get evaluation performance summary
*/
public function getPerformanceSummary(): array
{
$efficiency = 0.0;
if ($this->evaluatedRules > 0) {
$rulesPerMs = $this->evaluatedRules / max(1, $this->evaluationTime->toMilliseconds());
$efficiency = $rulesPerMs * 1000; // Rules per second
}
return [
'evaluation_time_ms' => $this->evaluationTime->toMilliseconds(),
'evaluated_rules' => $this->evaluatedRules,
'skipped_rules' => $this->skippedRules,
'error_count' => $this->errorCount,
'rules_per_second' => round($efficiency, 2),
'error_rate' => $this->evaluatedRules > 0
? round(($this->errorCount / $this->evaluatedRules) * 100, 2)
: 0.0,
];
}
/**
* Convert to array for serialization
*/
public function toArray(): array
{
return [
'timestamp' => $this->timestamp->toIsoString(),
'evaluation_time_ms' => $this->evaluationTime->toMilliseconds(),
'evaluated_rules' => $this->evaluatedRules,
'skipped_rules' => $this->skippedRules,
'error_count' => $this->errorCount,
'match_count' => $this->getMatchCount(),
'has_matches' => $this->hasMatches(),
'should_block' => $this->shouldBlock(),
'should_alert' => $this->shouldAlert(),
'highest_severity' => $this->getHighestSeverity(),
'threat_score' => $this->getThreatScore()->getValue(),
'overall_confidence' => $this->getOverallConfidence()->getValue(),
'matched_categories' => array_map(fn ($cat) => $cat->value, $this->getMatchedCategories()),
'matched_rule_ids' => $this->getMatchedRuleIds(),
'owasp_top10_matches' => count($this->getOwaspTop10Matches()),
'critical_matches' => count($this->getCriticalMatches()),
'high_severity_matches' => count($this->getHighSeverityMatches()),
'blocking_matches' => count($this->getBlockingMatches()),
'alerting_matches' => count($this->getAlertingMatches()),
'performance_summary' => $this->getPerformanceSummary(),
'matches' => array_map(fn (RuleMatch $match) => $match->toArray(), $this->matches),
'errors' => $this->errors,
];
}
}