Files
michaelschiemer/src/Framework/Waf/LayerResult.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

252 lines
7.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Waf;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Waf\ValueObjects\DetectionCollection;
use App\Framework\Waf\ValueObjects\ResultMetadata;
/**
* Result from a WAF security layer analysis
*/
final readonly class LayerResult
{
// Action constants for middleware compatibility
public const string ACTION_PASS = 'pass';
public const string ACTION_BLOCK = 'block';
public const string ACTION_SUSPICIOUS = 'suspicious';
public function __construct(
public string $layerName,
public Percentage $threatScore,
public LayerStatus $status,
public DetectionCollection $detections,
public string $reason,
public ?Duration $executionDuration = null,
public ?Timestamp $timestamp = null,
public ?ResultMetadata $metadata = null
) {
}
/**
* Create a result indicating a threat was detected
*/
public static function threat(
string $layerName,
string $reason,
LayerStatus $status = LayerStatus::THREAT_DETECTED,
array $detections = [],
?Duration $executionDuration = null,
?ResultMetadata $metadata = null
): self {
// Calculate threat score based on detections
$maxRiskScore = 0.0;
foreach ($detections as $detection) {
$threatScore = $detection->getThreatScore()->getValue();
if ($threatScore > $maxRiskScore) {
$maxRiskScore = $threatScore;
}
}
return new self(
layerName: $layerName,
threatScore: Percentage::from($maxRiskScore),
status: $status,
detections: DetectionCollection::fromArray($detections),
reason: $reason,
executionDuration: $executionDuration,
metadata: $metadata ?? ResultMetadata::empty()
);
}
/**
* Create a result indicating the request is clean
*/
public static function clean(string $layerName, string $reason = '', ?Duration $executionDuration = null, ?ResultMetadata $metadata = null): self
{
return new self(
layerName: $layerName,
threatScore: Percentage::from(0.0),
status: LayerStatus::CLEAN,
detections: DetectionCollection::empty(),
reason: $reason,
executionDuration: $executionDuration,
metadata: $metadata ?? ResultMetadata::empty()
);
}
/**
* Create a neutral result (layer couldn't determine threat level)
*/
public static function neutral(string $layerName, string $reason = '', ?ResultMetadata $metadata = null): self
{
return new self(
layerName: $layerName,
threatScore: Percentage::from(50.0), // Neutral score
status: LayerStatus::NEUTRAL,
detections: DetectionCollection::empty(),
reason: $reason,
metadata: $metadata ?? ResultMetadata::empty()
);
}
/**
* Create a result indicating suspicious activity (lower confidence threat)
*/
public static function suspicious(string $layerName, string $reason, array $detections = [], ?ResultMetadata $metadata = null): self
{
return new self(
layerName: $layerName,
threatScore: Percentage::from(60.0), // Moderate threat score
status: LayerStatus::SUSPICIOUS,
detections: DetectionCollection::fromArray($detections),
reason: $reason,
metadata: $metadata ?? ResultMetadata::empty()
);
}
/**
* Create a result indicating layer error/failure
*/
public static function error(string $layerName, string $reason, ?Duration $executionDuration = null, ?ResultMetadata $metadata = null): self
{
return new self(
layerName: $layerName,
threatScore: Percentage::from(0.0), // Don't penalize on error
status: LayerStatus::ERROR,
detections: DetectionCollection::empty(),
reason: $reason,
executionDuration: $executionDuration,
metadata: $metadata ?? ResultMetadata::empty()
);
}
/**
* Check if this result indicates a threat
*/
public function isThreat(): bool
{
return $this->status === LayerStatus::THREAT_DETECTED;
}
/**
* Check if this result is clean
*/
public function isClean(): bool
{
return $this->status === LayerStatus::CLEAN;
}
/**
* Check if layer had an error
*/
public function hasError(): bool
{
return $this->status === LayerStatus::ERROR;
}
/**
* Get recommended action based on layer result
*/
public function getAction(): string
{
return match ($this->status) {
LayerStatus::THREAT_DETECTED => self::ACTION_BLOCK,
LayerStatus::CLEAN => self::ACTION_PASS,
LayerStatus::NEUTRAL => self::ACTION_SUSPICIOUS,
LayerStatus::ERROR => self::ACTION_PASS, // Fail open
LayerStatus::SKIPPED => self::ACTION_PASS, // Continue processing
LayerStatus::TIMEOUT => self::ACTION_PASS, // Fail open on timeout
};
}
/**
* Get the reason/message for this result
*/
public function getMessage(): string
{
return $this->reason;
}
/**
* Get the layer name
*/
public function getLayerName(): string
{
return $this->layerName;
}
/**
* Check if result has detections
*/
public function hasDetections(): bool
{
return ! $this->detections->isEmpty();
}
/**
* Get the detections collection
*/
public function getDetections(): DetectionCollection
{
return $this->detections;
}
/**
* Create new result with execution duration
*/
public function withExecutionDuration(Duration $duration): self
{
return new self(
layerName: $this->layerName,
threatScore: $this->threatScore,
status: $this->status,
detections: $this->detections,
reason: $this->reason,
executionDuration: $duration,
timestamp: $this->timestamp,
metadata: $this->metadata
);
}
/**
* Create new result with timestamp
*/
public function withTimestamp(Timestamp $timestamp): self
{
return new self(
layerName: $this->layerName,
threatScore: $this->threatScore,
status: $this->status,
detections: $this->detections,
reason: $this->reason,
executionDuration: $this->executionDuration,
timestamp: $timestamp,
metadata: $this->metadata
);
}
/**
* Add metadata to result
*/
public function withMetadata(ResultMetadata $additionalMetadata): self
{
$combinedMetadata = $this->metadata?->merge($additionalMetadata) ?? $additionalMetadata;
return new self(
layerName: $this->layerName,
threatScore: $this->threatScore,
status: $this->status,
detections: $this->detections,
reason: $this->reason,
executionDuration: $this->executionDuration,
timestamp: $this->timestamp,
metadata: $combinedMetadata
);
}
}