- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
252 lines
7.4 KiB
PHP
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
|
|
);
|
|
}
|
|
}
|