From ed96fd766cdc57f0748ebfb2f0be601f32a5143e Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Thu, 13 Feb 2025 11:08:53 -0800 Subject: [PATCH 1/4] refactors audit notification to mail, adds test, ads check to scheduler --- .../Commands/SendUpcomingAuditReport.php | 18 ++++++----- app/Console/Kernel.php | 11 ++++--- .../Email/ExpiringAlertsNotificationTest.php | 30 +++++++++++++++++++ tests/Support/Settings.php | 6 ++++ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/app/Console/Commands/SendUpcomingAuditReport.php b/app/Console/Commands/SendUpcomingAuditReport.php index 7c81a37d3a..636230edc6 100644 --- a/app/Console/Commands/SendUpcomingAuditReport.php +++ b/app/Console/Commands/SendUpcomingAuditReport.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Mail\SendUpcomingAuditMail; use App\Models\Asset; use App\Models\Recipients\AlertRecipient; use App\Models\Setting; @@ -9,6 +10,7 @@ use App\Notifications\SendUpcomingAuditNotification; use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Mail; class SendUpcomingAuditReport extends Command { @@ -48,19 +50,19 @@ class SendUpcomingAuditReport extends Command $today = Carbon::now(); $interval_date = $today->copy()->addDays($interval); - $assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get(); - $this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline'); + $assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get(); + $this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline'); - if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) { + if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) { // Send a rollup to the admin, if settings dictate - $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) { - return new AlertRecipient($item); - }); + $recipients = collect(explode(',', $settings->alert_email)) + ->map(fn($item) => trim($item)) + ->all(); - $this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email); - \Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days)); + $this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email); + Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days)); } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 8d512f303b..664c8edc62 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -5,6 +5,7 @@ namespace App\Console; use App\Console\Commands\ImportLocations; use App\Console\Commands\ReEncodeCustomFieldNames; use App\Console\Commands\RestoreDeletedUsers; +use App\Models\Setting; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -18,12 +19,14 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command('snipeit:inventory-alerts')->daily(); - $schedule->command('snipeit:expiring-alerts')->daily(); - $schedule->command('snipeit:expected-checkin')->daily(); + if(Setting::getSettings()->alerts_enabled === 1) { + $schedule->command('snipeit:inventory-alerts')->daily(); + $schedule->command('snipeit:expiring-alerts')->daily(); + $schedule->command('snipeit:expected-checkin')->daily(); + $schedule->command('snipeit:upcoming-audits')->daily(); + } $schedule->command('snipeit:backup')->weekly(); $schedule->command('backup:clean')->daily(); - $schedule->command('snipeit:upcoming-audits')->daily(); $schedule->command('auth:clear-resets')->everyFifteenMinutes(); $schedule->command('saml:clear_expired_nonces')->weekly(); } diff --git a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php index 220f6543c8..57ad2cbcc3 100644 --- a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php +++ b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature\Notifications\Email; use App\Mail\ExpiringAssetsMail; use App\Mail\ExpiringLicenseMail; +use App\Mail\SendUpcomingAuditMail; use App\Models\Asset; use App\Models\License; use App\Models\Setting; @@ -88,4 +89,33 @@ class ExpiringAlertsNotificationTest extends TestCase return $mail->licenses->contains($expiredLicense) || $mail->licenses->contains($notExpiringLicense); }); } + + public function testAuditWarningThresholdEmailNotification() + { + $this->markIncompleteIfSqlite(); + Mail::fake(); + $this->settings->enableAlertEmail('admin@example.com'); + $this->settings->setAuditWarningDays(15); + + $alert_email = Setting::first()->alert_email; + + $upcomingAuditableAsset = Asset::factory()->create([ + 'next_audit_date' => now()->addDays(14)->format('Y-m-d'), + 'deleted_at' => null, + ]); + + $notAuditableAsset = Asset::factory()->create([ + 'next_audit_date' => now()->addDays(30)->format('Y-m-d'), + 'deleted_at' => null, + ]); + + $this->artisan('snipeit:upcoming-audits')->assertExitCode(0); + + Mail::assertSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $upcomingAuditableAsset) { + return $mail->hasTo($alert_email) && $mail->assets->contains($upcomingAuditableAsset); + }); + Mail::assertNotSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $notAuditableAsset) { + return $mail->hasTo($alert_email) && $mail->assets->contains($notAuditableAsset); + }); + } } \ No newline at end of file diff --git a/tests/Support/Settings.php b/tests/Support/Settings.php index 09d40974c1..8680d1c3b1 100644 --- a/tests/Support/Settings.php +++ b/tests/Support/Settings.php @@ -32,6 +32,12 @@ class Settings 'alert_threshold' => $days, ]); } + public function setAuditWarningDays(int $days): Settings + { + return $this->update([ + 'audit_warning_days' => $days, + ]); + } public function disableAlertEmail(): Settings { return $this->update([ From 13e1f4a127c34c0a3f18a178852e34094d73bcdf Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Thu, 13 Feb 2025 11:09:39 -0800 Subject: [PATCH 2/4] adds mailable --- app/Mail/SendUpcomingAuditMail.php | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 app/Mail/SendUpcomingAuditMail.php diff --git a/app/Mail/SendUpcomingAuditMail.php b/app/Mail/SendUpcomingAuditMail.php new file mode 100644 index 0000000000..03faa081eb --- /dev/null +++ b/app/Mail/SendUpcomingAuditMail.php @@ -0,0 +1,65 @@ +assets = $params; + $this->threshold = $threshold; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans_choice('mail.upcoming-audits', $this->assets->count(), ['count' => $this->assets->count(), 'threshold' => $this->threshold]), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + + + return new Content( + + markdown: 'notifications.markdown.upcoming-audits', + with: [ + 'assets' => $this->assets, + 'threshold' => $this->threshold, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} From 25c8449e86fb56312790a72aebaa341cd377a037 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Thu, 13 Feb 2025 11:14:18 -0800 Subject: [PATCH 3/4] remove unused --- app/Console/Commands/SendUpcomingAuditReport.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/Console/Commands/SendUpcomingAuditReport.php b/app/Console/Commands/SendUpcomingAuditReport.php index 636230edc6..ba84625ad2 100644 --- a/app/Console/Commands/SendUpcomingAuditReport.php +++ b/app/Console/Commands/SendUpcomingAuditReport.php @@ -4,11 +4,8 @@ namespace App\Console\Commands; use App\Mail\SendUpcomingAuditMail; use App\Models\Asset; -use App\Models\Recipients\AlertRecipient; use App\Models\Setting; -use App\Notifications\SendUpcomingAuditNotification; use Carbon\Carbon; -use Illuminate\Support\Facades\DB; use Illuminate\Console\Command; use Illuminate\Support\Facades\Mail; From 4c43a06eee30f0b20aaedaec434b97809a1dbc64 Mon Sep 17 00:00:00 2001 From: Godfrey M Date: Thu, 13 Feb 2025 11:19:49 -0800 Subject: [PATCH 4/4] add overdue asset to test --- .../Email/ExpiringAlertsNotificationTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php index 57ad2cbcc3..5bc034ba5a 100644 --- a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php +++ b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php @@ -104,6 +104,11 @@ class ExpiringAlertsNotificationTest extends TestCase 'deleted_at' => null, ]); + $overDueForAuditableAsset = Asset::factory()->create([ + 'next_audit_date' => now()->subDays(1)->format('Y-m-d'), + 'deleted_at' => null, + ]); + $notAuditableAsset = Asset::factory()->create([ 'next_audit_date' => now()->addDays(30)->format('Y-m-d'), 'deleted_at' => null, @@ -111,8 +116,8 @@ class ExpiringAlertsNotificationTest extends TestCase $this->artisan('snipeit:upcoming-audits')->assertExitCode(0); - Mail::assertSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $upcomingAuditableAsset) { - return $mail->hasTo($alert_email) && $mail->assets->contains($upcomingAuditableAsset); + Mail::assertSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $upcomingAuditableAsset, $overDueForAuditableAsset) { + return $mail->hasTo($alert_email) && ($mail->assets->contains($upcomingAuditableAsset) && $mail->assets->contains($overDueForAuditableAsset)); }); Mail::assertNotSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $notAuditableAsset) { return $mail->hasTo($alert_email) && $mail->assets->contains($notAuditableAsset);