CluPilotCloud/app/Models/SecurityBlock.php

99 lines
3.4 KiB
PHP

<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use App\Services\Security\HostFirewall;
use Database\Factories\SecurityBlockFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* Eine Sperre gegen eine Adresse — an genau einem Subjekt, einer Instanz oder
* einem Host (nie beiden). Entsteht in `BlockAddress` auch dann, wenn
* `HostFirewall::block()` selbst scheitert: der Datensatz IST die Sperre, die
* Firewall-Menge ist nur ihr aktueller Abdruck (siehe HostFirewall).
*/
class SecurityBlock extends Model
{
/** @use HasFactory<SecurityBlockFactory> */
use HasFactory, HasUuid;
protected $fillable = [
'host_id', 'instance_id', 'ip', 'reason', 'attempts', 'strikes',
'blocked_at', 'expires_at', 'released_at', 'released_by_type', 'released_by_id',
];
protected function casts(): array
{
return [
'attempts' => 'integer',
'strikes' => 'integer',
'blocked_at' => 'datetime',
'expires_at' => 'datetime',
'released_at' => 'datetime',
];
}
public function host(): BelongsTo
{
return $this->belongsTo(Host::class);
}
public function instance(): BelongsTo
{
return $this->belongsTo(Instance::class);
}
/** Wer vorzeitig aufgehoben hat — Operator oder Customer, wenn jemand. */
public function releasedBy(): MorphTo
{
return $this->morphTo();
}
/** Was gerade gilt: noch nicht aufgehoben und noch nicht abgelaufen. */
public function scopeActive(Builder $query): Builder
{
return $query->whereNull('released_at')->where('expires_at', '>', now());
}
/**
* Hebt die Sperre vorzeitig auf — am Datensatz UND, wenn das Subjekt einen
* erreichbaren Host hat, in dessen Firewall. `$by` ist null, wenn der
* Kernel sie ohnehin schon fallen ließ (Ablauf) und niemand sie manuell
* aufgehoben hat; sonst der Operator oder Customer, der den Knopf gedrückt
* hat (R23-Modal auf der aufrufenden Seite, nicht hier).
*/
/**
* Hebt die Sperre auf — und sagt, ob die Regel wirklich gefallen ist.
*
* Der Datensatz wird IMMER zuerst gespeichert, auch wenn der Host gerade
* nicht erreichbar ist: er ist die Sperre, die Firewall-Menge nur ihr
* Abdruck (siehe Klassenkopf), und `reapplyActiveBlocks()` traegt beim
* naechsten Lauf nur noch ein, was hier aktiv steht.
*
* Der Rueckgabewert ist deshalb NICHT „hat es geklappt", sondern „steht
* die Firewall schon nach". Er wird gebraucht, weil `HostFirewall::apply()`
* jeden Fehlschlag schluckt und nur ins Log meldet — ohne ihn faende der
* Mensch am Bildschirm „Sperre aufgehoben." vor, waehrend die Adresse
* weiter ausgesperrt ist und er sich das nicht erklaeren kann.
*/
public function release(?Model $by): bool
{
$this->releasedBy()->associate($by);
$this->released_at = now();
$this->save();
$host = $this->host ?? $this->instance?->host;
if ($host === null) {
return true; // Kein Host, keine Regel — nichts, was nachhinken koennte.
}
return app(HostFirewall::class)->release($host, $this->ip);
}
}