Skip to content

Commit 8e65182

Browse files
author
csavelief
committed
Create a timeline item for each alert
1 parent 8a332f9 commit 8e65182

7 files changed

Lines changed: 300 additions & 9 deletions

File tree

app/Http/Procedures/NotesProcedure.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function create(Request $request): array
3030

3131
/** @var User $user */
3232
$user = $request->user();
33-
$item = TimelineItem::createNote($user->id, $params['note']);
33+
$item = TimelineItem::createNote($user, $params['note']);
3434

3535
return [
3636
"msg" => "Your note has been saved!",

app/Jobs/ProcessIncomingEmails.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ private function cyberBuddy(User $user, \Webklex\PHPIMAP\Message $message): void
283283

284284
private function memex(User $user, \Webklex\PHPIMAP\Message $message): void
285285
{
286-
$item = TimelineItem::createNote($user->id, $message->getTextBody(), $message->getSubject()[0] ?? '');
286+
$item = TimelineItem::createNote($user, $message->getTextBody(), $message->getSubject()[0] ?? '');
287287
if ($message->hasAttachments()) {
288288
$collection = $this->getOrCreateCollection("privcol{$user->id}", 0);
289289
if ($collection) {

app/Listeners/DeleteAssetListener.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Enums\AssetTypesEnum;
66
use App\Events\DeleteAsset;
77
use App\Models\Asset;
8+
use App\Models\TimelineItem;
89
use App\Rules\IsValidAsset;
910
use App\Rules\IsValidDomain;
1011
use App\Rules\IsValidIpAddress;
@@ -28,6 +29,8 @@ public static function execute(User $user, string $asset): bool
2829
$assetType = AssetTypesEnum::RANGE;
2930
}
3031

32+
TimelineItem::deleteAlerts($user->id, $asset);
33+
3134
Asset::where('asset', $asset)
3235
->where('type', $assetType)
3336
->where('created_by', $user->id)

app/Listeners/EndVulnsScanListener.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use App\Models\Asset;
1111
use App\Models\Port;
1212
use App\Models\Scan;
13+
use App\Models\TimelineItem;
1314
use App\Models\YnhTrial;
1415
use Carbon\Carbon;
1516
use Illuminate\Auth\Passwords\PasswordBroker;
@@ -93,10 +94,10 @@ public static function sendEmailReport(YnhTrial $trial): void
9394

9495
$result = ApiUtils2::translate($alert->vulnerability);
9596

96-
if ($result ['error'] !== false) {
97+
if ($result['error'] !== false) {
9798
$vulnerability = $alert->vulnerability;
9899
} else {
99-
$vulnerability = $result ['response'];
100+
$vulnerability = $result['response'];
100101
}
101102
return "
102103
<h3>{$alert->title} {$level}</h3>
@@ -224,6 +225,7 @@ protected function handle2($event)
224225

225226
if ($scan) {
226227

228+
$this->createTimelineItem($scan);
227229
/** @var Asset $asset */
228230
$asset = $scan->asset()->firstOrFail();
229231
/** @var YnhTrial $trial */
@@ -235,7 +237,7 @@ protected function handle2($event)
235237
}
236238
}
237239

238-
private function handle3($event): void
240+
private function handle3(EndVulnsScan $event): void
239241
{
240242
$scan = $event->scan();
241243
$dropEvent = $event->drop();
@@ -301,6 +303,7 @@ private function handle3($event): void
301303
$product = $task['product'] ?? null;
302304
$ssl = $task['ssl'] ?? null;
303305

306+
/** @var Port $port */
304307
$port = $scan->port()->first();
305308
$port->service = $service;
306309
$port->product = $product;
@@ -449,4 +452,26 @@ private function taskOutput(string $taskId): array
449452
{
450453
return ApiUtils::task_get_scan_public($taskId);
451454
}
455+
456+
// Whatever happens during the ports scan, a vuln scan is triggered!
457+
private function createTimelineItem(Scan $scan): void
458+
{
459+
if (!$scan->portsScanHasEnded() || !$scan->vulnsScanHasEnded()) {
460+
Log::warning("Asset is still being scanned for scan {$scan->id}");
461+
} else {
462+
$scan->asset()
463+
->get()
464+
->map(function (Asset $asset) {
465+
TimelineItem::fetchAlerts($asset->created_by, null, null, 0, [
466+
[['asset_id', '=', $asset->id]],
467+
])->each(function (TimelineItem $item) {
468+
$item->deleteItem();
469+
$item->save();
470+
});
471+
return $asset;
472+
})
473+
->flatMap(fn(Asset $asset) => $asset->alerts()->get())
474+
->each(fn(Alert $alert) => TimelineItem::createAlert($alert->asset()->createdBy(), $scan, $alert));
475+
}
476+
}
452477
}

app/Models/TimelineItem.php

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Models;
44

5+
use App\Helpers\ApiUtilsFacade as ApiUtils2;
56
use App\User;
67
use Carbon\Carbon;
78
use Illuminate\Database\Eloquent\Model;
@@ -42,9 +43,113 @@ class TimelineItem extends Model
4243
'updated_at' => 'datetime',
4344
];
4445

45-
public static function createNote(int $ownedBy, string $body, string $subject = ''): TimelineItem
46+
public static function createAlert(User $user, Scan $scan, Alert $alert): TimelineItem
4647
{
47-
return self::createItem($ownedBy, 'note', Carbon::now(), 0, [
48+
$asset = $alert->asset();
49+
$port = $alert->port();
50+
51+
if (empty($alert->title)) {
52+
$title = '';
53+
} else {
54+
$result = ApiUtils2::translate($alert->title, 'fr');
55+
if ($result['error'] !== false) {
56+
$title = $alert->title;
57+
} else {
58+
$title = $result['response'];
59+
}
60+
}
61+
if (empty($alert->vulnerability)) {
62+
$vulnerability = '';
63+
} else {
64+
$result = ApiUtils2::translate($alert->vulnerability, 'fr');
65+
if ($result['error'] !== false) {
66+
$vulnerability = $alert->vulnerability;
67+
} else {
68+
$vulnerability = $result['response'];
69+
}
70+
}
71+
if (empty($alert->remediation)) {
72+
$remediation = '';
73+
} else {
74+
$result = ApiUtils2::translate($alert->remediation, 'fr');
75+
if ($result['error'] !== false) {
76+
$remediation = $alert->remediation;
77+
} else {
78+
$remediation = $result['response'];
79+
}
80+
}
81+
return self::createItem($user->id, 'alert', Carbon::now(), 0, [
82+
83+
// Ids
84+
'tenant_id' => $user->tenant_id,
85+
'asset_id' => $asset->id,
86+
'scan_id' => $scan->id,
87+
'port_id' => $port->id,
88+
'alert_id' => $alert->id,
89+
90+
// Asset
91+
'asset_name' => $asset->asset,
92+
'asset_type' => $asset->type->value,
93+
'asset_tld' => $asset->tld() ?? '',
94+
'asset_tags' => json_encode($asset->tags()->get()->pluck('tag')->unique()->sort()->values()->toArray()),
95+
'asset_ip' => $port->ip,
96+
97+
// Port
98+
'port_number' => $port->port,
99+
'port_protocol' => $port->protocol,
100+
'port_tags' => json_encode($port->tags()->get()->pluck('tag')->unique()->sort()->values()->toArray()),
101+
'port_service' => $port->service ?? '',
102+
'port_product' => $port->product ?? '',
103+
104+
// Hosting provider
105+
'hosting_service_description' => $port->hosting_service_description ?? '',
106+
'hosting_service_registry' => $port->hosting_service_registry ?? '',
107+
'hosting_service_asn' => $port->hosting_service_asn ?? '',
108+
'hosting_service_cidr' => $port->hosting_service_cidr ?? '',
109+
'hosting_service_country_code' => $port->hosting_service_country_code ?? '',
110+
'hosting_service_date' => $port->hosting_service_date ?? '',
111+
112+
// Vulnerability
113+
'vuln_type' => $alert->type,
114+
'vuln_vulnerability_en' => $alert->vulnerability ?? '',
115+
'vuln_vulnerability_fr' => $vulnerability,
116+
'vuln_remediation_en' => $alert->remediation ?? '',
117+
'vuln_remediation_fr' => $remediation,
118+
'vuln_level' => $alert->level ?? '',
119+
'vuln_uid' => $alert->uid ?? '',
120+
'vuln_cve_id' => $alert->cve_id ?? '',
121+
'vuln_cve_cvss' => $alert->cve_cvss ?? '',
122+
'vuln_cve_vendor' => $alert->cve_vendor ?? '',
123+
'vuln_cve_product' => $alert->cve_product ?? '',
124+
'vuln_title_en' => $alert->title ?? '',
125+
'vuln_title_fr' => $title,
126+
127+
// Misc.
128+
'country' => $port->country ?? '',
129+
'ssl' => $port->ssl ?? false,
130+
]);
131+
}
132+
133+
public static function fetchAlerts(?int $ownedBy = null, ?Carbon $createdAtOrAfter = null, ?Carbon $createdAtOrBefore = null, ?int $flags = null, array $ands = []): \Illuminate\Support\Collection
134+
{
135+
return self::fetchItems($ownedBy, 'alert', $createdAtOrAfter, $createdAtOrBefore, $flags, $ands);
136+
}
137+
138+
public static function deleteAlerts(int $ownedBy, string $asset): void
139+
{
140+
TimelineItem::fetchAlerts($ownedBy, null, null, 0, [
141+
[['asset_name', '=', $asset]],
142+
])->each(function (TimelineItem $item) {
143+
DB::transaction(function () use ($item) {
144+
$item->facts()->delete();
145+
$item->delete();
146+
});
147+
});
148+
}
149+
150+
public static function createNote(User $user, string $body, string $subject = ''): TimelineItem
151+
{
152+
return self::createItem($user->id, 'note', Carbon::now(), 0, [
48153
'body' => Str::limit(trim($body), 1000 - 3, '...'),
49154
'subject' => Str::limit(trim($subject), 1000 - 3, '...'),
50155
]);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration {
8+
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('t_facts', function (Blueprint $table) {
15+
$table->string('value', 5000)->nullable()->change();
16+
});
17+
}
18+
19+
/**
20+
* Reverse the migrations.
21+
*/
22+
public function down(): void
23+
{
24+
Schema::table('t_facts', function (Blueprint $table) {
25+
$table->string('value', 1000)->nullable()->change();
26+
});
27+
}
28+
};

0 commit comments

Comments
 (0)