Added dns updates
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
<x-filament-panels::page>
|
||||||
|
<form wire:submit="generateDMARC">
|
||||||
|
{{ $this->form }}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<x-filament::modal id="dmarc-record-modal" width="2xl">
|
||||||
|
<x-slot name="heading">
|
||||||
|
Generated DMARC Record
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<pre class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 text-sm overflow-x-auto whitespace-pre-wrap break-all">{{ $generatedRecord }}</pre>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
x-on:click="navigator.clipboard.writeText(@js($generatedRecord))"
|
||||||
|
class="fi-btn"
|
||||||
|
>
|
||||||
|
Copy to clipboard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</x-filament::modal>
|
||||||
|
<div wire:poll.5s>
|
||||||
|
</div>
|
||||||
|
</x-filament-panels::page>
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace VEximweb\Plugin\DnsTools\Filament\Resources\DmarcResource\Pages;
|
|
||||||
|
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Forms\Components\Select;
|
|
||||||
use Filament\Forms\Components\Repeater;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Forms\Components\Slider;
|
|
||||||
use Filament\Forms\Components\Placeholder;
|
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Notifications\Notification;
|
|
||||||
use VEximweb\Plugin\DnsTools\Filament\Resources\DmarcResource;
|
|
||||||
use VEximweb\Plugin\DnsTools\Models\DmarcCheck;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Services\DmarcCheckService;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\DmarcRecord;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Enums\DmarcPolicy;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Enums\DmarcAlignment;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Enums\DmarcReporting;
|
|
||||||
use VEximweb\Core\Data\Models\Domain;
|
|
||||||
|
|
||||||
class GenerateDmarc extends CreateRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = DmarcResource::class;
|
|
||||||
|
|
||||||
public Domain $domain;
|
|
||||||
public ?DmarcCheck $existingDmarc = null;
|
|
||||||
|
|
||||||
public function mount($record = null): void
|
|
||||||
{
|
|
||||||
if ($record) {
|
|
||||||
$this->domain = Domain::find($record);
|
|
||||||
$this->existingDmarc = DmarcCheck::where('domain', $this->domain->domain)->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
parent::mount();
|
|
||||||
|
|
||||||
if ($this->existingDmarc) {
|
|
||||||
$defaults = [
|
|
||||||
'policy' => $this->existingDmarc->policy ?? 'none',
|
|
||||||
'subdomain_policy' => $this->existingDmarc->subdomain_policy ?? null,
|
|
||||||
'adkim' => $this->existingDmarc->adkim ?? 'relaxed',
|
|
||||||
'aspf' => $this->existingDmarc->aspf ?? 'relaxed',
|
|
||||||
'reporting' => json_decode($this->existingDmarc->reporting ?? '["all"]', true),
|
|
||||||
'percentage' => $this->existingDmarc->percentage ?? 100,
|
|
||||||
't' => $this->existingDmarc->t ?? 'n',
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($this->existingDmarc->rua) {
|
|
||||||
$rua = json_decode($this->existingDmarc->rua, true);
|
|
||||||
if (is_array($rua)) {
|
|
||||||
$defaults['rua'] = collect($rua)->map(function ($item) {
|
|
||||||
return ['email' => str_replace('mailto:', '', $item)];
|
|
||||||
})->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->existingDmarc->ruf) {
|
|
||||||
$ruf = json_decode($this->existingDmarc->ruf, true);
|
|
||||||
if (is_array($ruf)) {
|
|
||||||
$defaults['ruf'] = collect($ruf)->map(function ($item) {
|
|
||||||
return ['email' => str_replace('mailto:', '', $item)];
|
|
||||||
})->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->form->fill($defaults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function form(Form $form): Form
|
|
||||||
{
|
|
||||||
$service = app(DmarcCheckService::class);
|
|
||||||
$addresses = $service->getDefaultReportingAddresses($this->domain->domain ?? config('app.domain'));
|
|
||||||
|
|
||||||
return $form
|
|
||||||
->schema([
|
|
||||||
Select::make('policy')
|
|
||||||
->label('Policy')
|
|
||||||
->options([
|
|
||||||
'none' => 'None (Monitor only)',
|
|
||||||
'quarantine' => 'Quarantine (Mark as spam)',
|
|
||||||
'reject' => 'Reject (Block delivery)',
|
|
||||||
])
|
|
||||||
->required()
|
|
||||||
->default('none')
|
|
||||||
->live(),
|
|
||||||
|
|
||||||
Select::make('subdomain_policy')
|
|
||||||
->label('Subdomain Policy')
|
|
||||||
->options([
|
|
||||||
'' => 'Inherit from main policy',
|
|
||||||
'none' => 'None (Monitor only)',
|
|
||||||
'quarantine' => 'Quarantine (Mark as spam)',
|
|
||||||
'reject' => 'Reject (Block delivery)',
|
|
||||||
])
|
|
||||||
->nullable(),
|
|
||||||
|
|
||||||
Repeater::make('rua')
|
|
||||||
->label('Aggregate Reports (RUA)')
|
|
||||||
->schema([
|
|
||||||
TextInput::make('email')
|
|
||||||
->label('Email')
|
|
||||||
->email()
|
|
||||||
->prefix('mailto:')
|
|
||||||
->required(),
|
|
||||||
])
|
|
||||||
->default(function () use ($addresses) {
|
|
||||||
if ($this->existingDmarc && $this->existingDmarc->rua) {
|
|
||||||
$rua = json_decode($this->existingDmarc->rua, true);
|
|
||||||
if (is_array($rua)) {
|
|
||||||
return collect($rua)->map(function ($item) {
|
|
||||||
return ['email' => str_replace('mailto:', '', $item)];
|
|
||||||
})->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [['email' => str_replace('mailto:', '', $addresses['rua'])]];
|
|
||||||
})
|
|
||||||
->addable()
|
|
||||||
->deletable()
|
|
||||||
->reorderable()
|
|
||||||
->columnSpanFull(),
|
|
||||||
|
|
||||||
Repeater::make('ruf')
|
|
||||||
->label('Forensic Reports (RUF)')
|
|
||||||
->schema([
|
|
||||||
TextInput::make('email')
|
|
||||||
->label('Email')
|
|
||||||
->email()
|
|
||||||
->prefix('mailto:')
|
|
||||||
->required(),
|
|
||||||
])
|
|
||||||
->default(function () use ($addresses) {
|
|
||||||
if ($this->existingDmarc && $this->existingDmarc->ruf) {
|
|
||||||
$ruf = json_decode($this->existingDmarc->ruf, true);
|
|
||||||
if (is_array($ruf)) {
|
|
||||||
return collect($ruf)->map(function ($item) {
|
|
||||||
return ['email' => str_replace('mailto:', '', $item)];
|
|
||||||
})->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [['email' => str_replace('mailto:', '', $addresses['ruf'])]];
|
|
||||||
})
|
|
||||||
->addable()
|
|
||||||
->deletable()
|
|
||||||
->reorderable()
|
|
||||||
->columnSpanFull(),
|
|
||||||
|
|
||||||
Select::make('adkim')
|
|
||||||
->label('DKIM Alignment')
|
|
||||||
->options([
|
|
||||||
'relaxed' => 'Relaxed (subdomains allowed)',
|
|
||||||
'strict' => 'Strict (exact match)',
|
|
||||||
])
|
|
||||||
->default('relaxed'),
|
|
||||||
|
|
||||||
Select::make('aspf')
|
|
||||||
->label('SPF Alignment')
|
|
||||||
->options([
|
|
||||||
'relaxed' => 'Relaxed (subdomains allowed)',
|
|
||||||
'strict' => 'Strict (exact match)',
|
|
||||||
])
|
|
||||||
->default('relaxed'),
|
|
||||||
|
|
||||||
Select::make('reporting')
|
|
||||||
->label('Failure Reporting (FO)')
|
|
||||||
->multiple()
|
|
||||||
->options([
|
|
||||||
'all' => 'All failures',
|
|
||||||
'any' => 'Any failure',
|
|
||||||
'dkim' => 'DKIM failures only',
|
|
||||||
'spf' => 'SPF failures only',
|
|
||||||
])
|
|
||||||
->default(['all']),
|
|
||||||
|
|
||||||
Slider::make('percentage')
|
|
||||||
->label('Enforcement Percentage')
|
|
||||||
->min(0)
|
|
||||||
->max(100)
|
|
||||||
->default(100),
|
|
||||||
|
|
||||||
Select::make('t')
|
|
||||||
->label('Testing Mode')
|
|
||||||
->options([
|
|
||||||
'n' => 'Off (Apply policy)',
|
|
||||||
'y' => 'On (Don\'t apply policy)',
|
|
||||||
])
|
|
||||||
->default('n'),
|
|
||||||
|
|
||||||
Placeholder::make('generated_record_preview')
|
|
||||||
->label('Preview')
|
|
||||||
->content(function ($get) {
|
|
||||||
try {
|
|
||||||
$settings = $this->prepareSettings($get);
|
|
||||||
$dmarcRecord = new DmarcRecord(
|
|
||||||
policy: DmarcPolicy::tryFrom($settings['policy']),
|
|
||||||
subdomainPolicy: $settings['subdomain_policy'] ? DmarcPolicy::tryFrom($settings['subdomain_policy']) : null,
|
|
||||||
rua: $settings['rua'],
|
|
||||||
ruf: $settings['ruf'],
|
|
||||||
adkim: DmarcAlignment::tryFrom($settings['adkim']),
|
|
||||||
aspf: DmarcAlignment::tryFrom($settings['aspf']),
|
|
||||||
reporting: array_map(fn($r) => DmarcReporting::tryFrom($r), $settings['reporting']),
|
|
||||||
percentage: (int)$settings['percentage'],
|
|
||||||
t: $settings['t'],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($this->domain) {
|
|
||||||
return "Name: _dmarc.{$this->domain->domain}\n" .
|
|
||||||
"Type: TXT\n" .
|
|
||||||
"Value: v=DMARC1; " . $dmarcRecord->toDnsRecord();
|
|
||||||
}
|
|
||||||
return "Please select a domain to preview";
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return "Error generating preview: " . $e->getMessage();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
->extraAttributes(['class' => 'font-mono text-sm bg-gray-50 dark:bg-gray-800 p-4 rounded'])
|
|
||||||
->columnSpanFull(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function prepareSettings(array $data): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'policy' => $data['policy'] ?? 'none',
|
|
||||||
'subdomain_policy' => $data['subdomain_policy'] ?? null,
|
|
||||||
'rua' => collect($data['rua'] ?? [])
|
|
||||||
->filter(fn($item) => !empty($item['email']))
|
|
||||||
->map(fn($item) => 'mailto:' . $item['email'])
|
|
||||||
->values()
|
|
||||||
->toArray(),
|
|
||||||
'ruf' => collect($data['ruf'] ?? [])
|
|
||||||
->filter(fn($item) => !empty($item['email']))
|
|
||||||
->map(fn($item) => 'mailto:' . $item['email'])
|
|
||||||
->values()
|
|
||||||
->toArray(),
|
|
||||||
'adkim' => $data['adkim'] ?? 'relaxed',
|
|
||||||
'aspf' => $data['aspf'] ?? 'relaxed',
|
|
||||||
'reporting' => $data['reporting'] ?? ['all'],
|
|
||||||
'percentage' => (int)($data['percentage'] ?? 100),
|
|
||||||
't' => $data['t'] ?? 'n',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function handleRecordCreation(array $data): \Illuminate\Database\Eloquent\Model
|
|
||||||
{
|
|
||||||
$settings = $this->prepareSettings($data);
|
|
||||||
|
|
||||||
$dmarcRecord = new DmarcRecord(
|
|
||||||
policy: DmarcPolicy::tryFrom($settings['policy']),
|
|
||||||
subdomainPolicy: $settings['subdomain_policy'] ? DmarcPolicy::tryFrom($settings['subdomain_policy']) : null,
|
|
||||||
rua: $settings['rua'],
|
|
||||||
ruf: $settings['ruf'],
|
|
||||||
adkim: DmarcAlignment::tryFrom($settings['adkim']),
|
|
||||||
aspf: DmarcAlignment::tryFrom($settings['aspf']),
|
|
||||||
reporting: array_map(fn($r) => DmarcReporting::tryFrom($r), $settings['reporting']),
|
|
||||||
percentage: (int)$settings['percentage'],
|
|
||||||
t: $settings['t'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$dnsRecord = 'v=DMARC1; ' . $dmarcRecord->toDnsRecord();
|
|
||||||
|
|
||||||
return DmarcCheck::updateOrCreate(
|
|
||||||
['domain' => $this->domain->domain],
|
|
||||||
[
|
|
||||||
'domain_id' => $this->domain->domain_id,
|
|
||||||
'record' => $dnsRecord,
|
|
||||||
'policy' => $settings['policy'],
|
|
||||||
'subdomain_policy' => $settings['subdomain_policy'],
|
|
||||||
'adkim' => $settings['adkim'],
|
|
||||||
'aspf' => $settings['aspf'],
|
|
||||||
'rua' => json_encode($settings['rua']),
|
|
||||||
'ruf' => json_encode($settings['ruf']),
|
|
||||||
'reporting' => json_encode($settings['reporting']),
|
|
||||||
'percentage' => (int)$settings['percentage'],
|
|
||||||
't' => $settings['t'],
|
|
||||||
'valid' => true,
|
|
||||||
'error_message' => null,
|
|
||||||
'last_checked_at' => now(),
|
|
||||||
'next_check_at' => now()->addHours(24),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getCreatedNotificationTitle(): ?string
|
|
||||||
{
|
|
||||||
return "DMARC record generated for {$this->domain->domain}";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getRedirectUrl(): string
|
|
||||||
{
|
|
||||||
return DmarcResource::getUrl('view', ['record' => $this->domain->getKey()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getHeaderActions(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
Action::make('back')
|
|
||||||
->label('Back to View')
|
|
||||||
->icon('heroicon-o-arrow-left')
|
|
||||||
->url(DmarcResource::getUrl('view', ['record' => $this->domain->getKey()])),
|
|
||||||
|
|
||||||
Action::make('back_to_list')
|
|
||||||
->label('Back to List')
|
|
||||||
->icon('heroicon-o-list-bullet')
|
|
||||||
->url(DmarcResource::getUrl('index')),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTitle(): string
|
|
||||||
{
|
|
||||||
$action = $this->existingDmarc ? 'Update' : 'Generate';
|
|
||||||
return "{$action} DMARC Record";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace VEximweb\Plugin\DnsTools\Filament\Pages\Dmarc;
|
|
||||||
|
|
||||||
use Filament\Resources\Pages\ListRecords;
|
|
||||||
use Filament\Tables\Table;
|
|
||||||
use Filament\Tables\Columns\TextColumn;
|
|
||||||
use Filament\Tables\Columns\BadgeColumn;
|
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Actions\BulkActionGroup;
|
|
||||||
use Filament\Notifications\Notification;
|
|
||||||
use VEximweb\Plugin\DnsTools\Filament\Resources\DmarcResource;
|
|
||||||
use VEximweb\Plugin\DnsTools\Models\DmarcCheck;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Services\DmarcCheckService;
|
|
||||||
use VEximweb\Core\Data\Models\Domain;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
|
|
||||||
class ListDmarc extends ListRecords
|
|
||||||
{
|
|
||||||
protected static string $resource = DmarcResource::class;
|
|
||||||
|
|
||||||
protected function getTableQuery(): Builder
|
|
||||||
{
|
|
||||||
return Domain::where('enabled', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function table(Table $table): Table
|
|
||||||
{
|
|
||||||
return $table
|
|
||||||
->query($this->getTableQuery())
|
|
||||||
->columns([
|
|
||||||
TextColumn::make('domain')
|
|
||||||
->label('Domain')
|
|
||||||
->searchable()
|
|
||||||
->sortable()
|
|
||||||
->size('lg')
|
|
||||||
->weight('bold'),
|
|
||||||
|
|
||||||
BadgeColumn::make('dmarc_status')
|
|
||||||
->label('DMARC Status')
|
|
||||||
->getStateUsing(function ($record) {
|
|
||||||
$dmarc = DmarcCheck::where('domain', $record->domain)->first();
|
|
||||||
if (!$dmarc) {
|
|
||||||
return 'Not Checked';
|
|
||||||
}
|
|
||||||
if (!$dmarc->valid) {
|
|
||||||
return 'No Record';
|
|
||||||
}
|
|
||||||
return $dmarc->getPolicyLabel();
|
|
||||||
})
|
|
||||||
->colors([
|
|
||||||
'success' => 'Monitor',
|
|
||||||
'warning' => 'Quarantine',
|
|
||||||
'danger' => 'Reject',
|
|
||||||
'gray' => ['Not Checked', 'No Record'],
|
|
||||||
]),
|
|
||||||
|
|
||||||
BadgeColumn::make('dmarc_policy')
|
|
||||||
->label('Policy')
|
|
||||||
->getStateUsing(function ($record) {
|
|
||||||
$dmarc = DmarcCheck::where('domain', $record->domain)->first();
|
|
||||||
return $dmarc?->policy ?? '-';
|
|
||||||
})
|
|
||||||
->colors([
|
|
||||||
'success' => 'none',
|
|
||||||
'warning' => 'quarantine',
|
|
||||||
'danger' => 'reject',
|
|
||||||
'gray' => '-',
|
|
||||||
]),
|
|
||||||
|
|
||||||
TextColumn::make('dmarc_last_checked')
|
|
||||||
->label('Last Checked')
|
|
||||||
->getStateUsing(function ($record) {
|
|
||||||
$dmarc = DmarcCheck::where('domain', $record->domain)->first();
|
|
||||||
return $dmarc?->last_checked_at;
|
|
||||||
})
|
|
||||||
->since()
|
|
||||||
->toggleable(),
|
|
||||||
|
|
||||||
TextColumn::make('exim_users_count')
|
|
||||||
->label('Accounts')
|
|
||||||
->counts('eximUsers')
|
|
||||||
->badge()
|
|
||||||
->color('info'),
|
|
||||||
])
|
|
||||||
->filters([
|
|
||||||
\Filament\Tables\Filters\SelectFilter::make('dmarc_status')
|
|
||||||
->label('DMARC Status')
|
|
||||||
->options([
|
|
||||||
'has_record' => 'Has DMARC Record',
|
|
||||||
'no_record' => 'No DMARC Record',
|
|
||||||
'not_checked' => 'Not Checked',
|
|
||||||
])
|
|
||||||
->query(function ($query, $data) {
|
|
||||||
if (empty($data['value'])) {
|
|
||||||
return $query;
|
|
||||||
}
|
|
||||||
|
|
||||||
return match($data['value']) {
|
|
||||||
'has_record' => $query->whereIn('domain',
|
|
||||||
DmarcCheck::where('valid', true)->pluck('domain')->toArray()
|
|
||||||
),
|
|
||||||
'no_record' => $query->whereIn('domain',
|
|
||||||
DmarcCheck::where('valid', false)->pluck('domain')->toArray()
|
|
||||||
),
|
|
||||||
'not_checked' => $query->whereNotIn('domain',
|
|
||||||
DmarcCheck::pluck('domain')->toArray()
|
|
||||||
),
|
|
||||||
default => $query,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
\Filament\Tables\Filters\SelectFilter::make('policy')
|
|
||||||
->label('Policy')
|
|
||||||
->options([
|
|
||||||
'none' => 'Monitor',
|
|
||||||
'quarantine' => 'Quarantine',
|
|
||||||
'reject' => 'Reject',
|
|
||||||
])
|
|
||||||
->query(function ($query, $data) {
|
|
||||||
if (empty($data['value'])) {
|
|
||||||
return $query;
|
|
||||||
}
|
|
||||||
$domains = DmarcCheck::where('policy', $data['value'])
|
|
||||||
->where('valid', true)
|
|
||||||
->pluck('domain')
|
|
||||||
->toArray();
|
|
||||||
return $query->whereIn('domain', $domains);
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
->actions([
|
|
||||||
Action::make('view')
|
|
||||||
->label('View DMARC')
|
|
||||||
->icon('heroicon-o-eye')
|
|
||||||
->color('info')
|
|
||||||
->url(fn ($record): string => DmarcResource::getUrl('view', ['record' => $record])),
|
|
||||||
|
|
||||||
Action::make('generate')
|
|
||||||
->label('Generate Record')
|
|
||||||
->icon('heroicon-o-document-plus')
|
|
||||||
->color('success')
|
|
||||||
->url(fn ($record): string => DmarcResource::getUrl('generate', ['record' => $record])),
|
|
||||||
|
|
||||||
Action::make('check_dns')
|
|
||||||
->label('Check DNS')
|
|
||||||
->icon('heroicon-o-arrow-path')
|
|
||||||
->color('warning')
|
|
||||||
->action(function ($record) {
|
|
||||||
$service = app(DmarcCheckService::class);
|
|
||||||
$result = $service->checkDomain($record->domain, $record->domain_id);
|
|
||||||
|
|
||||||
if ($result && $result->valid) {
|
|
||||||
Notification::make()
|
|
||||||
->success()
|
|
||||||
->title('DMARC Record Found')
|
|
||||||
->body("Policy: {$result->getPolicyLabel()}")
|
|
||||||
->send();
|
|
||||||
} else {
|
|
||||||
Notification::make()
|
|
||||||
->warning()
|
|
||||||
->title('No DMARC Record Found')
|
|
||||||
->body($result?->error_message ?? 'No DMARC record found in DNS')
|
|
||||||
->send();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
->bulkActions([
|
|
||||||
BulkActionGroup::make([
|
|
||||||
Action::make('check_all_dmarc')
|
|
||||||
->label('Check DMARC for Selected')
|
|
||||||
->icon('heroicon-o-arrow-path')
|
|
||||||
->action(function ($records) {
|
|
||||||
$service = app(DmarcCheckService::class);
|
|
||||||
$valid = 0;
|
|
||||||
|
|
||||||
foreach ($records as $record) {
|
|
||||||
$result = $service->checkDomain($record->domain, $record->domain_id);
|
|
||||||
if ($result && $result->valid) {
|
|
||||||
$valid++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Notification::make()
|
|
||||||
->success()
|
|
||||||
->title('DMARC Check Complete')
|
|
||||||
->body("Checked {$records->count()} domains, {$valid} have valid DMARC records")
|
|
||||||
->send();
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace VEximweb\Plugin\DnsTools\Filament\Pages\Dmarc;
|
|
||||||
|
|
||||||
use Filament\Resources\Pages\ViewRecord;
|
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Notifications\Notification;
|
|
||||||
use VEximweb\Plugin\DnsTools\Filament\Resources\DmarcResource;
|
|
||||||
use VEximweb\Plugin\DnsTools\Models\DmarcCheck;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Services\DmarcCheckService;
|
|
||||||
|
|
||||||
class ViewDmarc extends ViewRecord
|
|
||||||
{
|
|
||||||
protected static string $resource = DmarcResource::class;
|
|
||||||
|
|
||||||
public ?DmarcCheck $dmarc = null;
|
|
||||||
|
|
||||||
protected function mutateFormDataBeforeFill(array $data): array
|
|
||||||
{
|
|
||||||
// Get the DMARC record for this domain
|
|
||||||
$this->dmarc = DmarcCheck::where('domain', $this->record->domain)->first();
|
|
||||||
|
|
||||||
// If no cache or invalid, check now
|
|
||||||
if (!$this->dmarc || !$this->dmarc->valid) {
|
|
||||||
$service = app(DmarcCheckService::class);
|
|
||||||
$this->dmarc = $service->checkDomain($this->record->domain, $this->record->domain_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getHeaderActions(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
Action::make('check_dns')
|
|
||||||
->label('Check DNS Again')
|
|
||||||
->icon('heroicon-o-arrow-path')
|
|
||||||
->color('warning')
|
|
||||||
->action(function () {
|
|
||||||
$service = app(DmarcCheckService::class);
|
|
||||||
$this->dmarc = $service->checkDomain($this->record->domain, $this->record->domain_id);
|
|
||||||
|
|
||||||
Notification::make()
|
|
||||||
->success()
|
|
||||||
->title('DMARC Record Updated')
|
|
||||||
->body($this->dmarc?->valid ? 'Record found and updated' : 'No valid record found')
|
|
||||||
->send();
|
|
||||||
|
|
||||||
$this->refreshFormData();
|
|
||||||
}),
|
|
||||||
|
|
||||||
Action::make('generate')
|
|
||||||
->label('Generate New Record')
|
|
||||||
->icon('heroicon-o-document-plus')
|
|
||||||
->color('success')
|
|
||||||
->url(DmarcResource::getUrl('generate', ['record' => $this->record])),
|
|
||||||
|
|
||||||
Action::make('back')
|
|
||||||
->label('Back to List')
|
|
||||||
->icon('heroicon-o-arrow-left')
|
|
||||||
->url(DmarcResource::getUrl('index')),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getViewData(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'dmarc' => $this->dmarc,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTitle(): string
|
|
||||||
{
|
|
||||||
return "DMARC Record for {$this->record->domain}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace VEximweb\Plugin\DnsTools\Filament\Providers;
|
|
||||||
|
|
||||||
use Filament\Panel;
|
|
||||||
use Filament\PanelProvider;
|
|
||||||
use Filament\Support\Colors\Color;
|
|
||||||
use VEximweb\Plugin\DnsTools\Filament\Resources\DmarcResource;
|
|
||||||
|
|
||||||
class DnsToolsPanelProvider extends PanelProvider
|
|
||||||
{
|
|
||||||
public function panel(Panel $panel): Panel
|
|
||||||
{
|
|
||||||
return $panel
|
|
||||||
->id('dns-tools')
|
|
||||||
->path('dns-tools')
|
|
||||||
->colors([
|
|
||||||
'primary' => Color::Blue,
|
|
||||||
])
|
|
||||||
->resources([
|
|
||||||
DmarcResource::class,
|
|
||||||
])
|
|
||||||
->navigationGroups([
|
|
||||||
'DNS Tools',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace VEximweb\Plugin\DnsTools\Filament\Resources;
|
|
||||||
|
|
||||||
use Filament\Forms;
|
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Resources\Resource;
|
|
||||||
use Filament\Tables;
|
|
||||||
use Filament\Tables\Table;
|
|
||||||
use Filament\Notifications\Notification;
|
|
||||||
use VEximweb\Core\Data\Models\Domain;
|
|
||||||
use VEximweb\Plugin\DnsTools\Models\DmarcCheck;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Services\DmarcCheckService;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\DmarcRecord;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Enums\DmarcPolicy;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Enums\DmarcAlignment;
|
|
||||||
use VEximweb\Plugin\DnsTools\Dmarc\Enums\DmarcReporting;
|
|
||||||
use BackedEnum;
|
|
||||||
use VEximweb\Plugin\DnsTools\Filament\Pages\Dmarc\ListDmarc;
|
|
||||||
use VEximweb\Plugin\DnsTools\Filament\Pages\Dmarc\ViewDmarc;
|
|
||||||
use VEximweb\Plugin\DnsTools\Filament\Pages\Dmarc\GenerateDmarc;
|
|
||||||
|
|
||||||
class DmarcResource extends Resource
|
|
||||||
{
|
|
||||||
protected static ?string $model = Domain::class;
|
|
||||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'DNS Tools';
|
|
||||||
protected static ?string $navigationLabel = 'DMARC Management';
|
|
||||||
protected static ?string $pluralLabel = 'DMARC Management';
|
|
||||||
protected static ?int $navigationSort = 1;
|
|
||||||
protected static ?string $slug = 'dnstools/dmarc';
|
|
||||||
|
|
||||||
public static function getModel(): string
|
|
||||||
{
|
|
||||||
return Domain::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getPages(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'index' => ListDmarc::route('/'),
|
|
||||||
'view' => ViewDmarc::route('/{record}/view'),
|
|
||||||
'generate' => GenerateDmarc::route('/{record}/generate'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,32 +9,75 @@ use Filament\Forms\Components\Select;
|
|||||||
use Filament\Forms\Components\Textarea;
|
use Filament\Forms\Components\Textarea;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Hidden;
|
use Filament\Forms\Components\Hidden;
|
||||||
|
use VEximweb\Plugin\DnsTools\Models\SystemDomains as Domain;
|
||||||
|
|
||||||
class GenerateDmarcForm
|
class GenerateDmarcForm
|
||||||
{
|
{
|
||||||
protected static ?string $domain = null;
|
protected static ?Domain $domainRecord = null;
|
||||||
|
|
||||||
|
public static function extend(callable $components, callable $onSave): void
|
||||||
|
{
|
||||||
|
$existing = app()->bound('dmarcform.extenders')
|
||||||
|
? app('dmarcform.extenders')
|
||||||
|
: ['components' => [], 'hooks' => []];
|
||||||
|
|
||||||
|
$existing['components'][] = $components;
|
||||||
|
$existing['hooks'][] = $onSave;
|
||||||
|
|
||||||
|
app()->instance('dmarcform.extenders', $existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function runSaveHooks(mixed $record, array $data): void
|
||||||
|
{
|
||||||
|
if (! app()->bound('dmarcform.extenders')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (app('dmarcform.extenders')['hooks'] as $hook) {
|
||||||
|
$hook($record, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the domain for the form.
|
* Set the domain record for the form.
|
||||||
*/
|
*/
|
||||||
public static function setDomain(string $domain): void
|
public static function setDomainRecord(Domain $domain): void
|
||||||
{
|
{
|
||||||
static::$domain = $domain;
|
static::$domainRecord = $domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current domain.
|
* Get the current domain record.
|
||||||
*/
|
*/
|
||||||
protected static function getDomain(): string
|
protected static function getDomainRecord(): ?Domain
|
||||||
{
|
{
|
||||||
return static::$domain ?? 'example.com';
|
return static::$domainRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Filament form schema.
|
* The Filament form schema - now accepts Domain record instead of string
|
||||||
*/
|
*/
|
||||||
public static function schema(string $domain): array
|
public static function schema(Domain $domain): array
|
||||||
{
|
{
|
||||||
|
// Set the domain record for extensions
|
||||||
|
static::setDomainRecord($domain);
|
||||||
|
|
||||||
|
$extra = [];
|
||||||
|
|
||||||
|
if (app()->bound('dmarcform.extenders')) {
|
||||||
|
foreach (app('dmarcform.extenders')['components'] as $extender) {
|
||||||
|
// Pass the domain record directly to the extender
|
||||||
|
if (is_callable($extender)) {
|
||||||
|
$result = $extender($domain);
|
||||||
|
if (is_array($result)) {
|
||||||
|
$extra = array_merge($extra, $result);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$extra = array_merge($extra, $extender());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Section::make('Policy')
|
Section::make('Policy')
|
||||||
->columns(2)
|
->columns(2)
|
||||||
@@ -104,29 +147,31 @@ class GenerateDmarcForm
|
|||||||
'all' => 'All failures',
|
'all' => 'All failures',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
TextInput::make('dmarc_rua_localpart')
|
TextInput::make('dmarc_rua_localpart')
|
||||||
->label('Aggregate Reports Email')
|
->label('Aggregate Reports Email')
|
||||||
->helperText('Email address for aggregate reports (auto-appended with @' . $domain . ')')
|
->helperText('Email address for aggregate reports (auto-appended with @' . $domain->domain . ')')
|
||||||
->default('dmarc-reports')
|
->default('dmarc-reports')
|
||||||
->prefix('📊')
|
->prefix('📊')
|
||||||
->suffix('@' . $domain),
|
->suffix('@' . $domain->domain),
|
||||||
|
|
||||||
TextInput::make('dmarc_ruf_localpart')
|
TextInput::make('dmarc_ruf_localpart')
|
||||||
->label('Forensic Reports Email')
|
->label('Forensic Reports Email')
|
||||||
->helperText('Email address for forensic reports (auto-appended with @' . $domain . ')')
|
->helperText('Email address for forensic reports (auto-appended with @' . $domain->domain . ')')
|
||||||
->default('dmarc-forensic')
|
->default('dmarc-forensic')
|
||||||
->prefix('🔍')
|
->prefix('🔍')
|
||||||
->suffix('@' . $domain),
|
->suffix('@' . $domain->domain),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
...$extra,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate the form from the repository.
|
* Populate the form from the repository - now accepts domain record
|
||||||
*/
|
*/
|
||||||
public static function values(SettingRepositoryInterface $settings): array
|
public static function values(SettingRepositoryInterface $settings, Domain $domain): array
|
||||||
{
|
{
|
||||||
// Get settings for this domain (you might need to adjust this)
|
// Get settings for this domain
|
||||||
$rua_localpart = $settings->get('dmarc_rua_localpart', 'dmarc-reports');
|
$rua_localpart = $settings->get('dmarc_rua_localpart', 'dmarc-reports');
|
||||||
$ruf_localpart = $settings->get('dmarc_ruf_localpart', 'dmarc-forensic');
|
$ruf_localpart = $settings->get('dmarc_ruf_localpart', 'dmarc-forensic');
|
||||||
|
|
||||||
@@ -147,31 +192,31 @@ class GenerateDmarcForm
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the form back to the repository.
|
* Save the form back to the repository - now accepts domain record
|
||||||
*/
|
*/
|
||||||
public static function save(
|
public static function save(
|
||||||
SettingRepositoryInterface $settings,
|
SettingRepositoryInterface $settings,
|
||||||
array $data,
|
array $data,
|
||||||
string $domain,
|
Domain $domain,
|
||||||
): void {
|
): void {
|
||||||
foreach ([
|
foreach ([
|
||||||
'dmarc_policy',
|
'dmarc_policy',
|
||||||
'dmarc_subdomain_policy',
|
'dmarc_subdomain_policy',
|
||||||
'dmarc_np',
|
'dmarc_np',
|
||||||
'dmarc_psd',
|
'dmarc_psd',
|
||||||
'dmarc_t',
|
'dmarc_t',
|
||||||
'dmarc_percentage',
|
'dmarc_percentage',
|
||||||
'dmarc_report_interval',
|
'dmarc_report_interval',
|
||||||
'dmarc_adkim',
|
'dmarc_adkim',
|
||||||
'dmarc_aspf',
|
'dmarc_aspf',
|
||||||
'dmarc_rua_localpart',
|
'dmarc_rua_localpart',
|
||||||
'dmarc_ruf_localpart',
|
'dmarc_ruf_localpart',
|
||||||
] as $key) {
|
] as $key) {
|
||||||
$settings->set($key, $data[$key]);
|
$settings->set($key, $data[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$settings->set('dmarc_rua', [$data['dmarc_rua_localpart'] . '@' . $domain]);
|
$settings->set('dmarc_rua', [$data['dmarc_rua_localpart'] . '@' . $domain->domain]);
|
||||||
$settings->set('dmarc_ruf', [$data['dmarc_ruf_localpart'] . '@' . $domain]);
|
$settings->set('dmarc_ruf', [$data['dmarc_ruf_localpart'] . '@' . $domain->domain]);
|
||||||
$settings->set('dmarc_reporting', $data['dmarc_reporting'] ?? []);
|
$settings->set('dmarc_reporting', $data['dmarc_reporting'] ?? []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace VEximweb\Plugin\DnsTools\Filament\Resources\Dmarc\Pages;
|
||||||
|
|
||||||
|
use Filament\Resources\Pages\Page;
|
||||||
|
use Filament\Forms\Concerns\InteractsWithForms;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Filament\Schemas\Components\Section;
|
||||||
|
use Filament\Schemas\Components\Actions;
|
||||||
|
use Filament\Forms\Components\CheckboxList;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Forms\Components\Hidden;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
use BackedEnum;
|
||||||
|
use UnitEnum;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use VEximweb\Core\Data\Repositories\Interfaces\SettingRepositoryInterface;
|
||||||
|
use VEximweb\Plugin\DnsTools\Models\SystemDomains as Domain;
|
||||||
|
use VEximweb\Plugin\DnsTools\Services\DmarcRecordService;
|
||||||
|
|
||||||
|
class GenerateDmarcPage extends Page
|
||||||
|
{
|
||||||
|
use InteractsWithForms;
|
||||||
|
|
||||||
|
protected string $view = 'dns-tools::filament.pages.generate-dmarc';
|
||||||
|
|
||||||
|
public ?array $data = [];
|
||||||
|
|
||||||
|
public Domain $domain;
|
||||||
|
|
||||||
|
public ?string $generatedRecord = null;
|
||||||
|
|
||||||
|
public static function getResource(): string
|
||||||
|
{
|
||||||
|
return \VEximweb\Plugin\DnsTools\Filament\Resources\DnsToolsResource::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getNavigationIcon(): string|BackedEnum|null
|
||||||
|
{
|
||||||
|
return 'heroicon-o-document-text';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getNavigationGroup(): string|UnitEnum|null
|
||||||
|
{
|
||||||
|
return 'Generate DMARC';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'Generate DMARC Record';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getListeners(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'refreshNotifications' => 'refreshNotifications',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount(Domain $domain): void
|
||||||
|
{
|
||||||
|
$this->domain = $domain;
|
||||||
|
$domainId = $this->domain->id;
|
||||||
|
|
||||||
|
// Load existing settings if any
|
||||||
|
$settings = app(SettingRepositoryInterface::class);
|
||||||
|
$this->form->fill($this->getValues($settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form(Schema $schema): Schema
|
||||||
|
{
|
||||||
|
return $schema
|
||||||
|
->schema($this->getFormSchema())
|
||||||
|
->statePath('data');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the form schema as an array of components
|
||||||
|
*/
|
||||||
|
protected function getFormSchema(): array
|
||||||
|
{
|
||||||
|
$extra = [];
|
||||||
|
|
||||||
|
if (app()->bound('dmarcform.extenders')) {
|
||||||
|
foreach (app('dmarcform.extenders')['components'] as $extender) {
|
||||||
|
if (is_callable($extender)) {
|
||||||
|
$result = $extender($this->domain);
|
||||||
|
if (is_array($result)) {
|
||||||
|
$extra = array_merge($extra, $result);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$extra = array_merge($extra, $extender());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
Section::make('Policy')
|
||||||
|
->columns(2)
|
||||||
|
->schema([
|
||||||
|
Hidden::make('domain_id')
|
||||||
|
->default($this->domain->id),
|
||||||
|
Select::make('dmarc_policy')
|
||||||
|
->label('Policy')
|
||||||
|
->options([
|
||||||
|
'none' => 'None',
|
||||||
|
'quarantine' => 'Quarantine',
|
||||||
|
'reject' => 'Reject',
|
||||||
|
])
|
||||||
|
->required(),
|
||||||
|
|
||||||
|
Select::make('dmarc_subdomain_policy')
|
||||||
|
->label('Subdomain Policy')
|
||||||
|
->options([
|
||||||
|
'' => 'Inherit Policy',
|
||||||
|
'none' => 'None',
|
||||||
|
'quarantine' => 'Quarantine',
|
||||||
|
'reject' => 'Reject',
|
||||||
|
]),
|
||||||
|
|
||||||
|
Hidden::make('dmarc_np')
|
||||||
|
->default(''),
|
||||||
|
|
||||||
|
Hidden::make('dmarc_psd')
|
||||||
|
->default('n'),
|
||||||
|
|
||||||
|
Hidden::make('dmarc_t')
|
||||||
|
->default('n'),
|
||||||
|
|
||||||
|
Hidden::make('dmarc_percentage')
|
||||||
|
->default(100),
|
||||||
|
|
||||||
|
Hidden::make('dmarc_report_interval')
|
||||||
|
->default(86400),
|
||||||
|
]),
|
||||||
|
|
||||||
|
Section::make('Alignment')
|
||||||
|
->columns(2)
|
||||||
|
->schema([
|
||||||
|
Select::make('dmarc_adkim')
|
||||||
|
->label('DKIM Alignment')
|
||||||
|
->options([
|
||||||
|
'relaxed' => 'Relaxed',
|
||||||
|
'strict' => 'Strict',
|
||||||
|
]),
|
||||||
|
|
||||||
|
Select::make('dmarc_aspf')
|
||||||
|
->label('SPF Alignment')
|
||||||
|
->options([
|
||||||
|
'relaxed' => 'Relaxed',
|
||||||
|
'strict' => 'Strict',
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
Section::make('Reporting')
|
||||||
|
->schema([
|
||||||
|
CheckboxList::make('dmarc_reporting')
|
||||||
|
->label('Forensic Report Triggers')
|
||||||
|
->helperText('"Any" and "All" are mutually exclusive; DKIM/SPF can be combined.')
|
||||||
|
->options([
|
||||||
|
'any' => 'Any mechanism fails (DKIM or SPF, most common) ',
|
||||||
|
'all' => 'All mechanisms fail (Not recommended)',
|
||||||
|
'dkim' => 'DKIM fails',
|
||||||
|
'spf' => 'SPF fails',
|
||||||
|
])
|
||||||
|
->rule(fn () => function (string $attribute, $value, \Closure $fail) {
|
||||||
|
if (in_array('all', $value ?? []) && in_array('any', $value ?? [])) {
|
||||||
|
$fail("'Any' and 'All' cannot both be selected.");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
TextInput::make('dmarc_rua_localpart')
|
||||||
|
->label('Aggregate Reports Email')
|
||||||
|
->helperText('Email address for aggregate reports (auto-appended with @' . $this->domain->domain . ')')
|
||||||
|
->default('dmarc-reports')
|
||||||
|
->suffix('@' . $this->domain->domain),
|
||||||
|
|
||||||
|
TextInput::make('dmarc_ruf_localpart')
|
||||||
|
->label('Forensic Reports Email')
|
||||||
|
->helperText('Email address for forensic reports (auto-appended with @' . $this->domain->domain . ')')
|
||||||
|
->default('dmarc-forensic')
|
||||||
|
->suffix('@' . $this->domain->domain),
|
||||||
|
]),
|
||||||
|
|
||||||
|
...$extra,
|
||||||
|
|
||||||
|
Actions::make([
|
||||||
|
Action::make('generate')
|
||||||
|
->label('Generate DMARC Record')
|
||||||
|
->submit('generateDMARC'),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getValues(SettingRepositoryInterface $settings): array
|
||||||
|
{
|
||||||
|
$rua_localpart = $settings->get('dmarc_rua_localpart', 'dmarc-reports');
|
||||||
|
$ruf_localpart = $settings->get('dmarc_ruf_localpart', 'dmarc-forensic');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'domain_id' => $this->domain->domain_id,
|
||||||
|
'dmarc_policy' => $settings->get('dmarc_policy'),
|
||||||
|
'dmarc_subdomain_policy' => $settings->get('dmarc_subdomain_policy'),
|
||||||
|
'dmarc_np' => $settings->get('dmarc_np', ''),
|
||||||
|
'dmarc_psd' => $settings->get('dmarc_psd', 'n'),
|
||||||
|
'dmarc_t' => $settings->get('dmarc_t', 'n'),
|
||||||
|
'dmarc_percentage' => $settings->get('dmarc_percentage', 100),
|
||||||
|
'dmarc_report_interval' => $settings->get('dmarc_report_interval', 86400),
|
||||||
|
'dmarc_adkim' => $settings->get('dmarc_adkim'),
|
||||||
|
'dmarc_aspf' => $settings->get('dmarc_aspf'),
|
||||||
|
'dmarc_reporting' => $settings->get('dmarc_reporting', []),
|
||||||
|
'dmarc_rua_localpart' => $rua_localpart,
|
||||||
|
'dmarc_ruf_localpart' => $ruf_localpart,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateDMARC(DmarcRecordService $dmarc): void
|
||||||
|
{
|
||||||
|
$data = $this->form->getState();
|
||||||
|
\Log::debug('Form data:'. json_encode($data));
|
||||||
|
|
||||||
|
$this->generatedRecord = $dmarc->generate($this->domain, $data);
|
||||||
|
|
||||||
|
$domainName = $this->domain->domain ?? $this->domain['domain'] ?? (string) $this->domain;
|
||||||
|
|
||||||
|
\Log::debug('DMARC Event data:', [
|
||||||
|
'zone' => $domainName,
|
||||||
|
'name' => '_dmarc',
|
||||||
|
'content' => $this->generatedRecord
|
||||||
|
]);
|
||||||
|
|
||||||
|
event(new \App\Events\DmarcKeyGenerated(
|
||||||
|
zone: $domainName,
|
||||||
|
name: '_dmarc',
|
||||||
|
type: 'TXT',
|
||||||
|
content: $this->generatedRecord,
|
||||||
|
ttl: 3600,
|
||||||
|
operation: 'create'
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->dispatch('open-modal', id: 'dmarc-record-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Keep the static extend method for backward compatibility
|
||||||
|
public static function extend(callable $components, callable $onSave): void
|
||||||
|
{
|
||||||
|
$existing = app()->bound('dmarcform.extenders')
|
||||||
|
? app('dmarcform.extenders')
|
||||||
|
: ['components' => [], 'hooks' => []];
|
||||||
|
|
||||||
|
$existing['components'][] = $components;
|
||||||
|
$existing['hooks'][] = $onSave;
|
||||||
|
|
||||||
|
app()->instance('dmarcform.extenders', $existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,18 +14,19 @@ use Filament\Schemas\Schema;
|
|||||||
use Filament\Support\Icons\Heroicon;
|
use Filament\Support\Icons\Heroicon;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use VEximweb\Plugin\DnsTools\Filament\Resources\Dmarc\Pages\GenerateDmarcPage;
|
||||||
|
|
||||||
class DnsToolsResource extends Resource
|
class DnsToolsResource extends Resource
|
||||||
{
|
{
|
||||||
protected static ?string $model = Domain::class;
|
protected static ?string $model = Domain::class;
|
||||||
|
|
||||||
protected static ?string $slug = 'dnstools/domains';
|
protected static ?string $slug = 'dnstools';
|
||||||
|
|
||||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
protected static string|BackedEnum|null $navigationIcon = Heroicon::WrenchScrewdriver;
|
||||||
|
|
||||||
protected static ?string $recordTitleAttribute = 'domain';
|
protected static ?string $recordTitleAttribute = 'domain';
|
||||||
|
|
||||||
protected static string|\UnitEnum|null $navigationGroup = 'DNS Tools';
|
protected static string|\UnitEnum|null $navigationGroup = 'DNS';
|
||||||
|
|
||||||
protected static ?string $navigationLabel = 'DNS Tools';
|
protected static ?string $navigationLabel = 'DNS Tools';
|
||||||
|
|
||||||
@@ -81,10 +82,6 @@ class DnsToolsResource extends Resource
|
|||||||
return $schema;
|
return $schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function form_old(Schema $schema): Schema
|
|
||||||
{
|
|
||||||
return DomainForm::configure($schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function table(Table $table): Table
|
public static function table(Table $table): Table
|
||||||
{
|
{
|
||||||
@@ -168,6 +165,8 @@ class DnsToolsResource extends Resource
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => ListDomains::route('/'),
|
'index' => ListDomains::route('/'),
|
||||||
|
//'dmarc' => GenerateDmarcPage::route('/dmarc'),
|
||||||
|
'generate' => GenerateDmarcPage::route('/{domain}/generate-dmarc'),
|
||||||
//'create' => CreateDomain::route('/create'),
|
//'create' => CreateDomain::route('/create'),
|
||||||
//'edit' => EditDomain::route('/{record}/edit'),
|
//'edit' => EditDomain::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ use Filament\Notifications\Notification;
|
|||||||
use Filament\Actions\ActionGroup;
|
use Filament\Actions\ActionGroup;
|
||||||
use VEximweb\Plugin\DnsTools\Filament\Resources\Dmarc\Modals\GenerateDmarcForm;
|
use VEximweb\Plugin\DnsTools\Filament\Resources\Dmarc\Modals\GenerateDmarcForm;
|
||||||
use VEximweb\Core\Data\Repositories\Interfaces\SettingRepositoryInterface;
|
use VEximweb\Core\Data\Repositories\Interfaces\SettingRepositoryInterface;
|
||||||
|
use VEximweb\Plugin\DnsTools\Filament\Resources\DnsToolsResource;
|
||||||
|
|
||||||
|
|
||||||
class DomainsTable
|
class DomainsTable
|
||||||
{
|
{
|
||||||
@@ -208,9 +210,25 @@ class DomainsTable
|
|||||||
|
|
||||||
//EditAction::make(),
|
//EditAction::make(),
|
||||||
ActionGroup::make([
|
ActionGroup::make([
|
||||||
|
Action::make('generateDmarc')
|
||||||
|
->label('Generate DMARC')
|
||||||
|
->icon('heroicon-o-document-text')
|
||||||
|
->url(fn ($record) => DnsToolsResource::getUrl('generate', ['domain' => $record])),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
EditAction::make(),
|
EditAction::make(),
|
||||||
|
|
||||||
|
Action::make('dmarc')
|
||||||
|
->fillForm(fn (SettingRepositoryInterface $settings, $record) =>
|
||||||
|
GenerateDmarcForm::values($settings, $record) // Pass the record
|
||||||
|
)
|
||||||
|
->form(fn ($record) =>
|
||||||
|
GenerateDmarcForm::schema($record) // Pass the full record, not just domain string
|
||||||
|
)
|
||||||
|
->action(fn (array $data, SettingRepositoryInterface $settings, $record) =>
|
||||||
|
GenerateDmarcForm::save($settings, $data, $record) // Pass the record
|
||||||
|
),
|
||||||
|
/*
|
||||||
Action::make('dmarc')
|
Action::make('dmarc')
|
||||||
->fillForm(fn (SettingRepositoryInterface $settings) =>
|
->fillForm(fn (SettingRepositoryInterface $settings) =>
|
||||||
GenerateDmarcForm::values($settings)
|
GenerateDmarcForm::values($settings)
|
||||||
@@ -222,7 +240,7 @@ Action::make('dmarc')
|
|||||||
GenerateDmarcForm::save($settings, $data, $record->domain)
|
GenerateDmarcForm::save($settings, $data, $record->domain)
|
||||||
),
|
),
|
||||||
|
|
||||||
/*
|
*/ /*
|
||||||
Action::make('dmarc')
|
Action::make('dmarc')
|
||||||
->fillForm(fn (SettingRepositoryInterface $settings) =>
|
->fillForm(fn (SettingRepositoryInterface $settings) =>
|
||||||
GenerateDmarcForm::values($settings)
|
GenerateDmarcForm::values($settings)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace VEximweb\Plugin\DnsTools\Services;
|
||||||
|
|
||||||
|
use CbowOfRivia\DmarcRecordBuilder\DmarcRecord;
|
||||||
|
use VEximweb\Plugin\DnsTools\Models\SystemDomains as Domain;
|
||||||
|
|
||||||
|
class DmarcRecordService
|
||||||
|
{
|
||||||
|
public function build(Domain $domain, array $data): DmarcRecord
|
||||||
|
{
|
||||||
|
return new DmarcRecord(
|
||||||
|
policy: $data['dmarc_policy'],
|
||||||
|
subdomain_policy: $data['dmarc_subdomain_policy'] ?: null,
|
||||||
|
rua: 'mailto:' . $data['dmarc_rua_localpart'] . '@' . $domain->domain,
|
||||||
|
ruf: 'mailto:' . $data['dmarc_ruf_localpart'] . '@' . $domain->domain,
|
||||||
|
adkim: $data['dmarc_adkim'],
|
||||||
|
aspf: $data['dmarc_aspf'],
|
||||||
|
reporting: $data['dmarc_reporting'] ?? [],
|
||||||
|
np: $data['dmarc_np'] ?: null,
|
||||||
|
psd: $data['dmarc_psd'],
|
||||||
|
t: $data['dmarc_t'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(Domain $domain, array $data): string
|
||||||
|
{
|
||||||
|
return (string) $this->build($domain, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user