diff --git a/app/Notifications/AuditNotification.php b/app/Notifications/AuditNotification.php index abcd69c9b678..af94cefddcb3 100644 --- a/app/Notifications/AuditNotification.php +++ b/app/Notifications/AuditNotification.php @@ -10,6 +10,11 @@ use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; +use NotificationChannels\GoogleChat\Card; +use NotificationChannels\GoogleChat\GoogleChatChannel; +use NotificationChannels\GoogleChat\GoogleChatMessage; +use NotificationChannels\GoogleChat\Section; +use NotificationChannels\GoogleChat\Widgets\KeyValue; use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel; use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage; use Symfony\Component\Mime\Email; @@ -33,6 +38,10 @@ public function __construct($params) // $this->settings = Setting::getSettings(); $this->params = $params; + $item = $params['item']; + if (!$item || !is_object($item)) { + throw new \InvalidArgumentException('Notification requires a valid item.'); + } } /** @@ -51,6 +60,10 @@ public function via() $notifyBy[] = MicrosoftTeamsChannel::class; } + if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { + Log::debug('using google webhook'); + $notifyBy[] = GoogleChatChannel::class; + } return $notifyBy; } @@ -84,16 +97,21 @@ public static function toMicrosoftTeams($params) $location = $params['location'] ?? ''; $setting = Setting::getSettings(); + //if somehow a notification triggers without an item, bail out. + if(!$item || !is_object($item)){ + return null; + } + if(!Str::contains($setting->webhook_endpoint, 'workflows')) { return MicrosoftTeamsMessage::create() ->to($setting->webhook_endpoint) ->type('success') - ->title(class_basename(get_class($params['item'])) .' '.trans('general.audited')) + ->title(class_basename($item).' '.trans('general.audited')) ->addStartGroupToSection('activityText') ->fact(trans('mail.asset'), $item) ->fact(trans('general.administrator'), $admin_user->present()->viewUrl() . '|' . $admin_user->display_name); } - $message = class_basename(get_class($params['item'])) . ' Audited By '.$admin_user->display_name; + $message = class_basename(get_class($params['item'])) . trans('general.audited_by').' '.$admin_user->display_name; $details = [ trans('mail.asset') => htmlspecialchars_decode($item->display_name), trans('mail.notes') => $note ?: '', @@ -101,4 +119,35 @@ public static function toMicrosoftTeams($params) ]; return [$message, $details]; } + public function toGoogleChat() + { + $item = $this->params['item'] ?? null; + $admin_user = $this->params['admin'] ?? null; + $note = $this->params['note'] ?? ''; + $setting = $this->settings ?? Setting::getSettings(); + + $title = '' . class_basename($item) . ' ' . trans('general.audited') . ''; + $subtitle = htmlspecialchars_decode($item->display_name ?? ''); + \Log::debug('Google Chat audit payload', [ + 'title' => $title, + 'subtitle' => $subtitle, + 'admin' => $admin_user->display_name, + 'note' => $note, + ]); + return GoogleChatMessage::create() + ->to($setting->webhook_endpoint) + ->card( + Card::create() + ->header($title, $subtitle) + ->section( + Section::create( + KeyValue::create( + trans('general.audited_by'), + $admin_user?->display_name ?? '', + $note ?? '' + )->onClick(route('hardware.show', $item->id)) + ) + ) + ); + } } diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index 29be666fe7fd..87b8cc111ee6 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -39,6 +39,7 @@ 'accept_items' => 'Accept Items', 'audit' => 'Audit', 'audited' => 'Audited', + 'audited_by' => 'Audited By', 'audits' => 'Audits', 'audit_report' => 'Audit Log', 'assets' => 'Assets', diff --git a/tests/Feature/Notifications/Webhooks/NotificationTests.php b/tests/Feature/Notifications/Webhooks/NotificationTests.php new file mode 100644 index 000000000000..4537673f3013 --- /dev/null +++ b/tests/Feature/Notifications/Webhooks/NotificationTests.php @@ -0,0 +1,61 @@ + null, + ]); + $this->fail('Expected Error was not thrown'); + } catch (\Throwable $e) { + $this->assertInstanceOf(\InvalidArgumentException::class, $e); + $this->assertSame('Notification requires a valid item.', $e->getMessage()); + } + } + + public function testAuditNotificationFires() + { + $webhook_options = [ + 'enableSlackWebhook', + 'enableMicrosoftTeamsWebhook', + 'enableGoogleChatWebhook' + ]; + + Notification::fake(); + //tests every webhook option + foreach($webhook_options as $option) { + + $this->settings->{$option}(); + + $user = User::factory()->create(); + $item = Asset::factory()->create(); + + try { + $user->notify(new \App\Notifications\AuditNotification([ + 'item' => $item, + ])); + } catch (\InvalidArgumentException $e) { + $this->fail("AuditNotification threw for [{$option}]: {$e->getMessage()}"); + } + } + Notification::assertSentTimes(AuditNotification::class, count($webhook_options)); + } +} diff --git a/tests/Support/Settings.php b/tests/Support/Settings.php index 8ff8ba6945ed..4ea8ddb8aaac 100644 --- a/tests/Support/Settings.php +++ b/tests/Support/Settings.php @@ -93,6 +93,22 @@ public function enableSlackWebhook(): Settings 'webhook_channel' => '#it', ]); } + public function enableMicrosoftTeamsWebhook(): Settings + { + return $this->update([ + 'webhook_selected' => 'microsoft', + 'webhook_endpoint' => 'https://defaultd07ceb04416641fca1b9d3e0ac7600.84.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/1babbc7a3cdd4cf99c0fbed4367cf147/triggers/manual/paths/invoke?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=sVXmAYF5luz3oOEjvN-G7mJqEEvFjBTuAG8c3Qmkg', + ]); + } + + public function enableGoogleChatWebhook(): Settings + { + return $this->update([ + 'webhook_selected' => 'google', + 'webhook_botname' => 'SnipeBot5000', + 'webhook_endpoint' => 'https://chat.googleapis.com/v1/spaces/AAAATQckuT4/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=bZDaFDK4lO78HhHmC8BEWI6aAKkgqX2gFv2gHVAc8', + ]); + } public function disableSlackWebhook(): Settings {