From fd6cce5c3a55b2a23a941c54ec377678db69f624 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Mon, 15 Apr 2024 15:10:40 +0200 Subject: [PATCH 1/8] ITKDev: Start on better audit logging module --- composer.json | 1 + .../os2forms_audit/os2forms_audit.info.yml | 6 + modules/os2forms_audit/os2forms_audit.module | 0 .../src/Plugin/AuditLoggerInterface.php | 19 +++ .../os2forms_audit/src/Plugin/Database.php | 30 ++++ modules/os2forms_audit/src/Plugin/File.php | 29 ++++ modules/os2forms_audit/src/Plugin/Loki.php | 30 ++++ modules/os2forms_audit/src/Service/Logger.php | 5 + .../os2forms_audit/src/Service/LokiClient.php | 137 ++++++++++++++++++ 9 files changed, 257 insertions(+) create mode 100644 modules/os2forms_audit/os2forms_audit.info.yml create mode 100644 modules/os2forms_audit/os2forms_audit.module create mode 100644 modules/os2forms_audit/src/Plugin/AuditLoggerInterface.php create mode 100644 modules/os2forms_audit/src/Plugin/Database.php create mode 100644 modules/os2forms_audit/src/Plugin/File.php create mode 100644 modules/os2forms_audit/src/Plugin/Loki.php create mode 100644 modules/os2forms_audit/src/Service/Logger.php create mode 100644 modules/os2forms_audit/src/Service/LokiClient.php diff --git a/composer.json b/composer.json index 38ab90b0..1bc66ccc 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ }, "require": { "php": "^8.1", + "ext-curl": "*", "ext-dom": "*", "ext-soap": "*", "cweagans/composer-patches": "^1.6.5", diff --git a/modules/os2forms_audit/os2forms_audit.info.yml b/modules/os2forms_audit/os2forms_audit.info.yml new file mode 100644 index 00000000..43864a34 --- /dev/null +++ b/modules/os2forms_audit/os2forms_audit.info.yml @@ -0,0 +1,6 @@ +name: OS2Forms Audit +type: module +description: 'OS2Forms Audit Module (log all events to external service)' +core_version_requirement: ^8 || ^9 +dependencies: + - 'drupal:admin_audit_trail' diff --git a/modules/os2forms_audit/os2forms_audit.module b/modules/os2forms_audit/os2forms_audit.module new file mode 100644 index 00000000..e69de29b diff --git a/modules/os2forms_audit/src/Plugin/AuditLoggerInterface.php b/modules/os2forms_audit/src/Plugin/AuditLoggerInterface.php new file mode 100644 index 00000000..ca78507e --- /dev/null +++ b/modules/os2forms_audit/src/Plugin/AuditLoggerInterface.php @@ -0,0 +1,19 @@ +notice('Entity with ID @id is written.', ['@id' => $entity->id()]); + } + +} diff --git a/modules/os2forms_audit/src/Plugin/File.php b/modules/os2forms_audit/src/Plugin/File.php new file mode 100644 index 00000000..e8a21762 --- /dev/null +++ b/modules/os2forms_audit/src/Plugin/File.php @@ -0,0 +1,29 @@ +notice('Entity with ID @id is written.', ['@id' => $entity->id()]); + } + +} diff --git a/modules/os2forms_audit/src/Service/Logger.php b/modules/os2forms_audit/src/Service/Logger.php new file mode 100644 index 00000000..1f6760e3 --- /dev/null +++ b/modules/os2forms_audit/src/Service/Logger.php @@ -0,0 +1,5 @@ +entrypoint = $this->getEntrypoint($apiConfig['entrypoint']); + $this->customCurlOptions = 'curl_options' ?? []; + + if (isset($apiConfig['auth']['basic'])) { + $this->basicAuth = (2 === count($apiConfig['auth']['basic'])) ? $apiConfig['auth']['basic'] : []; + } + } + + /** + * Send a log message to Loki ingestion endpoint. + * + * Message format sendt to loki (https://grafana.com/docs/loki/latest/reference/api/#ingest-logs) + * in the following json format. + * { + * "Streams": [ + * { + * "stream": { + * "label": "value" + * }, + * "values": [ + * [ "", "", ] + * ] + * } + * ] + * } + * + * @param string $label + * @param int $epoch + * @param string $line + * @param array $metadata + * + * @return void + * + * @throws \JsonException + */ + public function send(string $label, int $epoch, string $line, array $metadata = []): void { + $this->sendPacket([ + 'streams' => [ + 'stream' => [ + 'label' => $label, + ], + 'values' => [ + [ $epoch, $line, $metadata], + ] + ], + ]); + } + + /** + * Ensure the URL to entry point is correct + * + * @param string $entrypoint + * Entry point URL. + * + * @return string + * The entry point URL formatted without a slash in the ending. + */ + private function getEntrypoint(string $entrypoint): string { + if (!str_ends_with($entrypoint, '/')) { + return $entrypoint; + } + + return substr($entrypoint, 0, -1); + } + + /** + * Send a packet to the Loki ingestion endpoint. + * + * @param array $packet + * The packet to send. + * + * @return void + * + * @throws \JsonException + * If unable to encode the packet to JSON. + * @throws \LogicException + * If unable to connect to the Loki endpoint. + */ + private function sendPacket(array $packet): void { + $payload = json_encode($packet, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $url = sprintf('%s/loki/api/v1/push', $this->entrypoint); + + if (NULL === $this->connection) { + $this->connection = curl_init($url); + + if (FALSE === $this->connection) { + throw new \LogicException('Unable to connect to ' . $url); + } + } + + if (FALSE !== $this->connection) { + $curlOptions = array_replace( + [ + CURLOPT_CONNECTTIMEOUT_MS => 500, + CURLOPT_TIMEOUT_MS => 200, + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($payload), + ], + ], + $this->customCurlOptions + ); + + if (!empty($this->basicAuth)) { + $curlOptions[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC; + $curlOptions[CURLOPT_USERPWD] = implode(':', $this->basicAuth); + } + + curl_setopt_array($this->connection, $curlOptions); + curl_exec($this->connection); + } + } + +} From b6248bd84f1dc85df6b446dfa23edbeb79f272c4 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Mon, 15 Apr 2024 16:12:44 +0200 Subject: [PATCH 2/8] ITKDev: Added custom pluing manager for audit log extensions --- CHANGELOG.md | 3 + modules/os2forms_audit/README.md | 42 +++++++++ .../os2forms_audit/os2forms_audit.info.yml | 4 +- .../os2forms_audit.links.menu.yml | 5 ++ modules/os2forms_audit/os2forms_audit.module | 3 + .../os2forms_audit/os2forms_audit.routing.yml | 7 ++ .../os2forms_audit.services.yml | 4 + .../src/Annotation/AuditLoggerProvider.php | 43 +++++++++ .../os2forms_audit/src/Form/SettingsForm.php | 88 +++++++++++++++++++ .../AuditLoggerInterface.php | 8 +- .../src/Plugin/{ => AuditLogger}/Database.php | 13 ++- .../src/Plugin/{ => AuditLogger}/File.php | 11 ++- .../src/Plugin/{ => AuditLogger}/Loki.php | 13 ++- .../src/Plugin/LoggerManager.php | 36 ++++++++ modules/os2forms_audit/src/Service/Logger.php | 18 ++++ .../os2forms_audit/src/Service/LokiClient.php | 28 +++++- 16 files changed, 297 insertions(+), 29 deletions(-) create mode 100644 modules/os2forms_audit/README.md create mode 100644 modules/os2forms_audit/os2forms_audit.links.menu.yml create mode 100644 modules/os2forms_audit/os2forms_audit.routing.yml create mode 100644 modules/os2forms_audit/os2forms_audit.services.yml create mode 100644 modules/os2forms_audit/src/Annotation/AuditLoggerProvider.php create mode 100644 modules/os2forms_audit/src/Form/SettingsForm.php rename modules/os2forms_audit/src/Plugin/{ => AuditLogger}/AuditLoggerInterface.php (51%) rename modules/os2forms_audit/src/Plugin/{ => AuditLogger}/Database.php (57%) rename modules/os2forms_audit/src/Plugin/{ => AuditLogger}/File.php (66%) rename modules/os2forms_audit/src/Plugin/{ => AuditLogger}/Loki.php (56%) create mode 100644 modules/os2forms_audit/src/Plugin/LoggerManager.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7edbe9b4..e97685e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- Added new audit logging module (os2form_audit). + ## [3.15.7] 2024-08-15 - [#123](https://github.com/OS2Forms/os2forms/pull/123) @@ -69,6 +71,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa Fix coding standards. - [#102](https://github.com/OS2Forms/os2forms/pull/102) Fix array access with `purge_days` configuration. +- Added digital post test command. ## [3.14.1] 2024-01-16 diff --git a/modules/os2forms_audit/README.md b/modules/os2forms_audit/README.md new file mode 100644 index 00000000..4607955a --- /dev/null +++ b/modules/os2forms_audit/README.md @@ -0,0 +1,42 @@ +# OS2Form Audit Module + +OS2Form Audit is a Drupal module that helps track changes and perform audit on +OS2Form events. + +## Requirements +- +- PHP 8.1 or higher +- Drupal 8 or 9 +- Composer for managing PHP dependencies + +## Features +- +- Detailed audit log entries. +- Support for all OS2Form entity types. +- Easily extendable for custom use case. + +## Installation + +### Composer + +### Drush + +### Admin UI + +Alternatively, you can enable this module via Drupal admin UI by visiting the +extent page (`/admin/modules`). + +## Configuration + +Navigate to `/admin/config/os2form/audit` to configure the module. + +Some additional (brief) information on Usage, Extending/Overriding, and +Maintainers could go here. + +## Usage + +Describe how to use this module after installation. + +## Extending and Overriding + +Describe how to extend or override this module's functionality. diff --git a/modules/os2forms_audit/os2forms_audit.info.yml b/modules/os2forms_audit/os2forms_audit.info.yml index 43864a34..297e6f4d 100644 --- a/modules/os2forms_audit/os2forms_audit.info.yml +++ b/modules/os2forms_audit/os2forms_audit.info.yml @@ -1,6 +1,4 @@ name: OS2Forms Audit type: module description: 'OS2Forms Audit Module (log all events to external service)' -core_version_requirement: ^8 || ^9 -dependencies: - - 'drupal:admin_audit_trail' +core_version_requirement: ^8 || ^9 || ^10 diff --git a/modules/os2forms_audit/os2forms_audit.links.menu.yml b/modules/os2forms_audit/os2forms_audit.links.menu.yml new file mode 100644 index 00000000..7032a261 --- /dev/null +++ b/modules/os2forms_audit/os2forms_audit.links.menu.yml @@ -0,0 +1,5 @@ +os2forms_audit.admin_settings: + title: 'OS2Forms Audit settings' + parent: system.admin_config_system + description: 'Settings for the OS2Forms Audit module' + route_name: os2forms_audit.admin_settings diff --git a/modules/os2forms_audit/os2forms_audit.module b/modules/os2forms_audit/os2forms_audit.module index e69de29b..50cce95b 100644 --- a/modules/os2forms_audit/os2forms_audit.module +++ b/modules/os2forms_audit/os2forms_audit.module @@ -0,0 +1,3 @@ +get('config.factory'), + $container->get('plugin.manager.os2forms_audit_logger') + ); + } + + /** + * The name of the configuration setting. + * + * @var string + */ + public static string $configName = 'os2forms_audit.settings'; + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames(): array { + return [self::$configName]; + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'os2forms_audit_admin_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $config = $this->config(self::$configName); + + $plugins = $this->loggerManager->getDefinitions(); + + $form['enabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enabled'), + '#description' => $this->t('E'), + '#default_value' => $config->get('enabled'), + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + parent::submitForm($form, $form_state); + + $this->config(self::$configName) + ->set('enabled', $form_state->getValue('enabled')) + ->save(); + } + +} diff --git a/modules/os2forms_audit/src/Plugin/AuditLoggerInterface.php b/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php similarity index 51% rename from modules/os2forms_audit/src/Plugin/AuditLoggerInterface.php rename to modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php index ca78507e..ed2ff916 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLoggerInterface.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php @@ -1,13 +1,16 @@ notice('Entity with ID @id is written.', ['@id' => $entity->id()]); + \Drupal::logger('os2forms_audit')->notice('Entity with ID @id is written.', ['@id' => $entity->id()]); } } diff --git a/modules/os2forms_audit/src/Plugin/File.php b/modules/os2forms_audit/src/Plugin/AuditLogger/File.php similarity index 66% rename from modules/os2forms_audit/src/Plugin/File.php rename to modules/os2forms_audit/src/Plugin/AuditLogger/File.php index e8a21762..e1af7270 100644 --- a/modules/os2forms_audit/src/Plugin/File.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/File.php @@ -1,26 +1,25 @@ notice('Entity with ID @id is written.', ['@id' => $entity->id()]); + \Drupal::logger('os2forms_audit')->notice('Entity with ID @id is written.', ['@id' => $entity->id()]); } } diff --git a/modules/os2forms_audit/src/Plugin/LoggerManager.php b/modules/os2forms_audit/src/Plugin/LoggerManager.php new file mode 100644 index 00000000..2a761e71 --- /dev/null +++ b/modules/os2forms_audit/src/Plugin/LoggerManager.php @@ -0,0 +1,36 @@ +alterInfo('os2forms_audit_logger_info'); + $this->setCacheBackend($cache_backend, 'os2forms_audit_logger_plugins'); + } + +} diff --git a/modules/os2forms_audit/src/Service/Logger.php b/modules/os2forms_audit/src/Service/Logger.php index 1f6760e3..35c9dd51 100644 --- a/modules/os2forms_audit/src/Service/Logger.php +++ b/modules/os2forms_audit/src/Service/Logger.php @@ -1,5 +1,23 @@ loggerManager->getDefinitions(); + + } } diff --git a/modules/os2forms_audit/src/Service/LokiClient.php b/modules/os2forms_audit/src/Service/LokiClient.php index 16020685..cec555ed 100644 --- a/modules/os2forms_audit/src/Service/LokiClient.php +++ b/modules/os2forms_audit/src/Service/LokiClient.php @@ -3,16 +3,36 @@ namespace Drupal\os2forms_audit\Service; /** - * Class LokiClient + * Class LokiClient. * - * This is based/inspired by https://github.com/itspire/monolog-loki + * This is based/inspired by https://github.com/itspire/monolog-loki. */ class LokiClient { + /** + * @var string|null + */ protected ?string $entrypoint; + + /** + * Basic authentication username and password. + * + * @var array + */ protected array $basicAuth = []; + + /** + * Custom options for CURL command. + * + * @var array + */ protected array $customCurlOptions = []; + /** + * Curl handler. + * + * @var \CurlHandle|null + */ private ?\CurlHandle $connection = NULL; public function __construct( @@ -60,8 +80,8 @@ public function send(string $label, int $epoch, string $line, array $metadata = 'label' => $label, ], 'values' => [ - [ $epoch, $line, $metadata], - ] + [$epoch, $line, $metadata], + ], ], ]); } From 65e5fe7d26282ad136d230c09fee6223ee1e5b5b Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Thu, 18 Apr 2024 09:02:15 +0200 Subject: [PATCH 3/8] ITKDev: Tried to make local task for plugin configuration --- .../os2forms_audit.links.menu.yml | 2 +- .../os2forms_audit.links.task.yml | 5 + modules/os2forms_audit/os2forms_audit.module | 5 +- .../os2forms_audit/os2forms_audit.routing.yml | 9 +- .../src/Annotation/AuditLoggerProvider.php | 5 +- .../src/Controller/LocalTasksController.php | 55 ++++++++ .../src/Form/PluginSettingsForm.php | 123 ++++++++++++++++++ .../os2forms_audit/src/Form/SettingsForm.php | 28 +++- .../src/Plugin/AuditLogger/File.php | 51 +++++++- .../src/Plugin/AuditLogger/Loki.php | 105 ++++++++++++++- .../{Database.php => Watchdog.php} | 10 +- .../src/Plugin/Derivative/LocalTask.php | 63 +++++++++ modules/os2forms_audit/src/Service/Logger.php | 4 +- .../os2forms_audit/src/Service/LokiClient.php | 12 +- 14 files changed, 450 insertions(+), 27 deletions(-) create mode 100644 modules/os2forms_audit/os2forms_audit.links.task.yml create mode 100644 modules/os2forms_audit/src/Controller/LocalTasksController.php create mode 100644 modules/os2forms_audit/src/Form/PluginSettingsForm.php rename modules/os2forms_audit/src/Plugin/AuditLogger/{Database.php => Watchdog.php} (64%) create mode 100644 modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php diff --git a/modules/os2forms_audit/os2forms_audit.links.menu.yml b/modules/os2forms_audit/os2forms_audit.links.menu.yml index 7032a261..40417ecc 100644 --- a/modules/os2forms_audit/os2forms_audit.links.menu.yml +++ b/modules/os2forms_audit/os2forms_audit.links.menu.yml @@ -2,4 +2,4 @@ os2forms_audit.admin_settings: title: 'OS2Forms Audit settings' parent: system.admin_config_system description: 'Settings for the OS2Forms Audit module' - route_name: os2forms_audit.admin_settings + route_name: os2forms_audit.plugin_settings_local_tasks diff --git a/modules/os2forms_audit/os2forms_audit.links.task.yml b/modules/os2forms_audit/os2forms_audit.links.task.yml new file mode 100644 index 00000000..88b649d4 --- /dev/null +++ b/modules/os2forms_audit/os2forms_audit.links.task.yml @@ -0,0 +1,5 @@ +os2forms_audit.plugin_settings_tasks: + title: 'Dynamic tasks' + route_name: os2forms_audit.plugin_settings_local_tasks + base_route: os2forms_audit.plugin_settings_local_tasks + deriver: Drupal\os2forms_audit\Plugin\Derivative\LocalTask diff --git a/modules/os2forms_audit/os2forms_audit.module b/modules/os2forms_audit/os2forms_audit.module index 50cce95b..29c53cf9 100644 --- a/modules/os2forms_audit/os2forms_audit.module +++ b/modules/os2forms_audit/os2forms_audit.module @@ -1,3 +1,6 @@ formBuilder = $formBuilder; + $this->configFactory = $configFactory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): LocalTasksController|static { + return new static( + $container->get('form_builder'), + $container->get('config.factory'), + ); + } + + /** + * Get dynamic tasks. + * + * @param string|null $type (optional) + * The type of form to retrieve. Defaults to NULL. + * + * @return array + * An array containing the form definition. + */ + public function dynamicTasks(string $type = NULL): array { + if (empty($type)) { + return $this->formBuilder->getForm('\Drupal\os2forms_audit\Form\SettingsForm'); + } + + return $this->formBuilder->getForm('\Drupal\os2forms_audit\Form\PluginSettingsForm', $type); + } + +} diff --git a/modules/os2forms_audit/src/Form/PluginSettingsForm.php b/modules/os2forms_audit/src/Form/PluginSettingsForm.php new file mode 100644 index 00000000..f344ecf3 --- /dev/null +++ b/modules/os2forms_audit/src/Form/PluginSettingsForm.php @@ -0,0 +1,123 @@ +manager = $manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): PluginSettingsForm|ConfigFormBase|static { + return new static( + $container->get('config.factory'), + $container->get('plugin.manager.os2forms_audit_logger') + ); + } + + /** + * {@inheritdoc} + */ + public static function getConfigName(): string { + return 'os2forms_audit.plugin_settings'; + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames(): array { + return [$this->getConfigName()]; + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return $this->getConfigName() . '_settings_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $plugin_id = $form_state->getBuildInfo()['args'][0]; + $instance = $this->getPluginInstance($plugin_id); + $form = $instance->buildConfigurationForm($form, $form_state); + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state): void { + $plugin_id = $form_state->getBuildInfo()['args'][0]; + $instance = $this->getPluginInstance($plugin_id); + $instance->validateConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + $plugin_id = $form_state->getBuildInfo()['args'][0]; + $instance = $this->getPluginInstance($plugin_id); + $instance->submitConfigurationForm($form, $form_state); + + $config = $this->config($this->getConfigName()); + $config->set($plugin_id, $instance->getConfiguration()); + $config->save(); + + parent::submitForm($form, $form_state); + } + + /** + * Returns plugin instance for a given plugin id. + * + * @param string $plugin_id + * The plugin_id for the plugin instance. + * + * @return object + * Plugin instance. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function getPluginInstance(string $plugin_id): object { + $configuration = $this->config($this->getConfigName())->get($plugin_id); + + return $this->manager->createInstance($plugin_id, $configuration ?? []); + } + +} diff --git a/modules/os2forms_audit/src/Form/SettingsForm.php b/modules/os2forms_audit/src/Form/SettingsForm.php index 3e102ab6..de605dd2 100644 --- a/modules/os2forms_audit/src/Form/SettingsForm.php +++ b/modules/os2forms_audit/src/Form/SettingsForm.php @@ -63,12 +63,27 @@ public function buildForm(array $form, FormStateInterface $form_state): array { $config = $this->config(self::$configName); $plugins = $this->loggerManager->getDefinitions(); + ksort($plugins); + $options = array_map(function ($plugin) { + /** @var \Drupal\Core\StringTranslation\TranslatableMarkup $title */ + $title = $plugin['title']; + return $title->render(); + }, $plugins); - $form['enabled'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Enabled'), - '#description' => $this->t('E'), - '#default_value' => $config->get('enabled'), + $form['provider'] = [ + '#type' => 'select', + '#title' => $this->t('Log provider'), + '#description' => $this->t('Select the logger provider you which to use'), + '#options' => $options, + '#default_value' => $config->get('provider'), + ]; + + $form['fallback'] = [ + '#type' => 'select', + '#title' => $this->t('Fallback Log provider'), + '#description' => $this->t('Select the logger provider you which to use, if the main provider fails'), + '#options' => $options, + '#default_value' => $config->get('fallback'), ]; return parent::buildForm($form, $form_state); @@ -81,7 +96,8 @@ public function submitForm(array &$form, FormStateInterface $form_state): void { parent::submitForm($form, $form_state); $this->config(self::$configName) - ->set('enabled', $form_state->getValue('enabled')) + ->set('provider', $form_state->getValue('provider')) + ->set('fallback', $form_state->getValue('fallback')) ->save(); } diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/File.php b/modules/os2forms_audit/src/Plugin/AuditLogger/File.php index e1af7270..1ac11388 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/File.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/File.php @@ -2,19 +2,27 @@ namespace Drupal\os2forms_audit\Plugin\AuditLogger; +use Drupal\Component\Plugin\ConfigurableInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginBase; +use Drupal\Core\Plugin\PluginFormInterface; /** * Writes entities to a file. * * @AuditLoggerProvider( * id = "file", - * title = @Translation("File logger"), + * title = @Translation("File"), * description = @Translation("Writes entities to a file.") * ) */ -class File extends PluginBase implements AuditLoggerInterface { +class File extends PluginBase implements AuditLoggerInterface, PluginFormInterface, ConfigurableInterface{ + + public function __construct(array $configuration, $plugin_id, $plugin_definition) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->setConfiguration($configuration); + } /** * {@inheritdoc} @@ -25,4 +33,43 @@ public function write(EntityInterface $entity): void { file_put_contents('path_to_your_file.txt', serialize($entity)); } + public function getConfiguration(): array { + return $this->configuration; + } + + public function setConfiguration(array $configuration): static { + $this->configuration = $configuration + $this->defaultConfiguration(); + return $this; + } + + public function defaultConfiguration(): array { + return [ + 'path' => '/tmp/os2forms_audit.log', + ]; + } + + public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { + + $form['path'] = [ + '#type' => 'textfield', + '#title' => $this->t('Full path to the audit log file to store entries in'), + '#default_value' => $this->configuration['path'], + ]; + + return $form; + } + + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + // @todo Implement validateConfigurationForm() method. + } + + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + if (!$form_state->getErrors()) { + $values = $form_state->getValues(); + $configuration = [ + 'path' => $values['path'], + ]; + $this->setConfiguration($configuration); + } + } } diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php b/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php index b5c92cbc..0f075c70 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php @@ -2,28 +2,127 @@ namespace Drupal\os2forms_audit\Plugin\AuditLogger; +use Drupal\Component\Plugin\ConfigurableInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginBase; +use Drupal\Core\Plugin\PluginFormInterface; /** * Stores entities in the database. * * @AuditLoggerProvider( * id = "loki", - * title = @Translation("Loki logger"), + * title = @Translation("Grafana Loki"), * description = @Translation("Store entity data in Loki.") * ) */ -class Loki extends PluginBase implements AuditLoggerInterface { +class Loki extends PluginBase implements AuditLoggerInterface, PluginFormInterface, ConfigurableInterface { + + public function __construct(array $configuration, $plugin_id, $plugin_definition) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->setConfiguration($configuration); + } /** * {@inheritdoc} */ public function write(EntityInterface $entity): void { - // Code to write the $entity data to a file as an audit entry. // Then log the action like this: \Drupal::logger('os2forms_audit')->notice('Entity with ID @id is written.', ['@id' => $entity->id()]); } + public function getConfiguration(): array { + return $this->configuration; + } + + public function setConfiguration(array $configuration): static { + $this->configuration = $configuration + $this->defaultConfiguration(); + return $this; + } + + public function defaultConfiguration(): array { + return [ + 'entrypoint' => 'http://loki:3100' + ]; + } + + public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { + $form['entrypoint'] = [ + '#type' => 'url', + '#title' => $this->t('Entry Point URL'), + '#required' => TRUE, + '#default_value' => $this->configuration['entrypoint'], + ]; + + $form['auth'] = [ + '#tree' => TRUE, + 'username' => [ + '#type' => 'textfield', + '#title' => $this->t('Username'), + '#required' => TRUE, + '#default_value' => $this->configuration['auth']['username'], + ], + 'password' => [ + '#type' => 'password', + '#title' => $this->t('Password'), + '#required' => TRUE, + '#default_value' => $this->configuration['auth']['password'], + ], + ]; + + $form['curl_options'] = [ + '#type' => 'textfield', + '#title' => $this->t('cURL Options'), + '#default_value' => $this->configuration['curl_options'], + ]; + + return $form; + } + + public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void { + $values = $form_state->getValues(); + + // Validate entrypoint. + if (filter_var($values['entrypoint'], FILTER_VALIDATE_URL) === FALSE) { + $form_state->setErrorByName('entrypoint', $this->t('Invalid URL.')); + } + + // Validate auth username. + if (empty($values['auth']['username'])) { + $form_state->setErrorByName('auth][username', $this->t('Username is required.')); + } + + // Validate auth password. + if (empty($values['auth']['password'])) { + $form_state->setErrorByName('auth][password', $this->t('Password is required.')); + } + + $curlOptions = array_filter(explode(',', $values['curl_options'])); + foreach ($curlOptions as $option) { + [$key,] = explode(' =>', $option); + $key = trim($key); + if (!defined($key)) { + $form_state->setErrorByName('curl_options', $this->t('%option is not a valid cURL option.', ['%option' => $key])); + break; + } + } + } + + public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void { + if (!$form_state->getErrors()) { + $values = $form_state->getValues(); + $configuration = [ + 'entrypoint' => $values['entrypoint'], + 'auth' => [ + 'username' => $values['auth']['username'], + 'password' => $values['auth']['password'] + ], + 'curl_options' => $values['curl_options'], + ]; + $this->setConfiguration($configuration); + } + } + } diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/Database.php b/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php similarity index 64% rename from modules/os2forms_audit/src/Plugin/AuditLogger/Database.php rename to modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php index d282bb27..dac2cf68 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/Database.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php @@ -3,18 +3,19 @@ namespace Drupal\os2forms_audit\Plugin\AuditLogger; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginBase; /** * Stores entities in the database. * * @AuditLoggerProvider( - * id = "database", - * title = @Translation("Database logger"), + * id = "watchdog", + * title = @Translation("Watchdog"), * description = @Translation("Store entity data in the database.") * ) */ -class Database extends PluginBase implements AuditLoggerInterface { +class Watchdog extends PluginBase implements AuditLoggerInterface { /** * {@inheritdoc} @@ -23,7 +24,6 @@ public function write(EntityInterface $entity): void { // Code to write the $entity data to a file as an audit entry. // Then log the action like this: - \Drupal::logger('os2forms_audit')->notice('Entity with ID @id is written.', ['@id' => $entity->id()]); + \Drupal::logger('os2forms_audit')->info('Entity with ID @id is written.', ['@id' => $entity->id()]); } - } diff --git a/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php b/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php new file mode 100644 index 00000000..c1aa555f --- /dev/null +++ b/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php @@ -0,0 +1,63 @@ +get('plugin.manager.os2forms_audit_logger') + ); + } + + /** + * {@inheritdoc} + * + * @throws \ReflectionException + */ + public function getDerivativeDefinitions($base_plugin_definition) { + $plugins = $this->loggerManager->getDefinitions(); + ksort($plugins); + + // Sadly it seems that it is not possible to just invalidate the + // deriver/menu cache stuff. To get the local tasks menu links. So instead + // of clear all caches on settings save to only show selected plugins, we + // show em all. + $options = array_map(function ($plugin) { + // Only the plugins that provides configuration options. + $reflector = new \ReflectionClass($plugin['class']); + if ($reflector->implementsInterface('Drupal\Component\Plugin\ConfigurableInterface')) { + /** @var \Drupal\Core\StringTranslation\TranslatableMarkup $title */ + $title = $plugin['title']; + return $title->render(); + } + }, $plugins); + + foreach (['settings' => 'Settings'] + $options as $plugin => $title) { + $this->derivatives[$plugin] = $base_plugin_definition; + $this->derivatives[$plugin]['title'] = $title; + $this->derivatives[$plugin]['route_parameters'] = ['type' => $plugin]; + if ($plugin === 'settings') { + $this->derivatives[$plugin]['route_parameters']['type'] = ''; + } + } + + return $this->derivatives; + } + +} diff --git a/modules/os2forms_audit/src/Service/Logger.php b/modules/os2forms_audit/src/Service/Logger.php index 35c9dd51..bdc656e1 100644 --- a/modules/os2forms_audit/src/Service/Logger.php +++ b/modules/os2forms_audit/src/Service/Logger.php @@ -2,7 +2,7 @@ namespace Drupal\os2forms_audit\Service; -use Drupal\os2forms_audit\LoggerManager; +use Drupal\os2forms_audit\Plugin\LoggerManager; /** * Class Logger @@ -16,7 +16,7 @@ public function __construct( ) { } - public function log() { + public function log(): void { $this->loggerManager->getDefinitions(); } diff --git a/modules/os2forms_audit/src/Service/LokiClient.php b/modules/os2forms_audit/src/Service/LokiClient.php index cec555ed..eea69bf4 100644 --- a/modules/os2forms_audit/src/Service/LokiClient.php +++ b/modules/os2forms_audit/src/Service/LokiClient.php @@ -35,11 +35,17 @@ class LokiClient { */ private ?\CurlHandle $connection = NULL; + /** + * Default constructor + * . + * @param array $apiConfig + * Configuration for the loki connection. + */ public function __construct( array $apiConfig, ) { $this->entrypoint = $this->getEntrypoint($apiConfig['entrypoint']); - $this->customCurlOptions = 'curl_options' ?? []; + $this->customCurlOptions = $apiConfig['curl_options'] ?? []; if (isset($apiConfig['auth']['basic'])) { $this->basicAuth = (2 === count($apiConfig['auth']['basic'])) ? $apiConfig['auth']['basic'] : []; @@ -65,9 +71,13 @@ public function __construct( * } * * @param string $label + * Loki global label to use. * @param int $epoch + * Unix epoch in nanoseconds * @param string $line + * The log line to send. * @param array $metadata + * Structured metadata. * * @return void * From 111ed53c71be71f43cb89671819e1d09209aecb5 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Thu, 18 Apr 2024 14:58:36 +0200 Subject: [PATCH 4/8] ITKDev: Added drush command to send line into audit log --- .../os2forms_audit.services.yml | 10 +++++ .../src/Commands/AuditLogDrushCommands.php | 41 +++++++++++++++++ .../src/Controller/LocalTasksController.php | 5 ++- .../AuditLogger/AuditLoggerInterface.php | 16 ++++--- .../src/Plugin/AuditLogger/File.php | 45 ++++++++++++++----- .../src/Plugin/AuditLogger/Loki.php | 31 +++++++++---- .../src/Plugin/AuditLogger/Watchdog.php | 13 +++--- .../src/Plugin/Derivative/LocalTask.php | 5 ++- .../src/Plugin/LoggerManager.php | 8 ++-- modules/os2forms_audit/src/Service/Logger.php | 30 +++++++++++-- .../os2forms_audit/src/Service/LokiClient.php | 37 ++++++++------- 11 files changed, 179 insertions(+), 62 deletions(-) create mode 100644 modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php diff --git a/modules/os2forms_audit/os2forms_audit.services.yml b/modules/os2forms_audit/os2forms_audit.services.yml index a477926d..b6b589e9 100644 --- a/modules/os2forms_audit/os2forms_audit.services.yml +++ b/modules/os2forms_audit/os2forms_audit.services.yml @@ -2,3 +2,13 @@ services: plugin.manager.os2forms_audit_logger: class: Drupal\os2forms_audit\Plugin\LoggerManager parent: default_plugin_manager + + os2forms_audit.logger: + class: Drupal\os2forms_audit\Service\Logger + arguments: ['@plugin.manager.os2forms_audit_logger', '@config.factory'] + + os2forms_audit.commands: + class: Drupal\os2forms_audit\Commands\Os2FormsAuditDrushCommands + arguments: ['@os2forms_audit.logger'] + tags: + - { name: drush.command } diff --git a/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php b/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php new file mode 100644 index 00000000..133569ab --- /dev/null +++ b/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php @@ -0,0 +1,41 @@ +auditLogger->log(time(), $log_message, []); + } + +} diff --git a/modules/os2forms_audit/src/Controller/LocalTasksController.php b/modules/os2forms_audit/src/Controller/LocalTasksController.php index e64b02f1..3b43f239 100644 --- a/modules/os2forms_audit/src/Controller/LocalTasksController.php +++ b/modules/os2forms_audit/src/Controller/LocalTasksController.php @@ -7,6 +7,9 @@ use Drupal\Core\Form\FormBuilderInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +/** + * Class to handle local taks tabs callbacks. + */ class LocalTasksController extends ControllerBase { /** @@ -38,7 +41,7 @@ public static function create(ContainerInterface $container): LocalTasksControll /** * Get dynamic tasks. * - * @param string|null $type (optional) + * @param string|null $type * The type of form to retrieve. Defaults to NULL. * * @return array diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php b/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php index ed2ff916..579d0e56 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php @@ -2,10 +2,7 @@ namespace Drupal\os2forms_audit\Plugin\AuditLogger; -use Drupal\Component\Plugin\ConfigurableInterface; use Drupal\Component\Plugin\PluginInspectionInterface; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Plugin\PluginFormInterface; /** * Interface for AuditLogger plugins. @@ -13,11 +10,16 @@ interface AuditLoggerInterface extends PluginInspectionInterface { /** - * Write the entity data. + * Logs a message with optional metadata. * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity to be audited. + * @param int $timestamp + * The timestamp of the log entry. + * @param string $line + * The log message. + * @param array $metadata + * Additional metadata associated with the log entry. Defaults to an empty + * array. */ - public function write(EntityInterface $entity); + public function log(int $timestamp, string $line, array $metadata = []): void; } diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/File.php b/modules/os2forms_audit/src/Plugin/AuditLogger/File.php index 1ac11388..c0227ffb 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/File.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/File.php @@ -3,7 +3,6 @@ namespace Drupal\os2forms_audit\Plugin\AuditLogger; use Drupal\Component\Plugin\ConfigurableInterface; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Plugin\PluginFormInterface; @@ -17,7 +16,7 @@ * description = @Translation("Writes entities to a file.") * ) */ -class File extends PluginBase implements AuditLoggerInterface, PluginFormInterface, ConfigurableInterface{ +class File extends PluginBase implements AuditLoggerInterface, PluginFormInterface, ConfigurableInterface { public function __construct(array $configuration, $plugin_id, $plugin_definition) { parent::__construct($configuration, $plugin_id, $plugin_definition); @@ -27,49 +26,75 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition /** * {@inheritdoc} */ - public function write(EntityInterface $entity): void { + public function log(int $timestamp, string $line, array $metadata = []): void { // Code to write the entity to a file. // This is just a placeholder and won't write the data. - file_put_contents('path_to_your_file.txt', serialize($entity)); + file_put_contents( + $this->configuration['file'], + json_encode([ + 'epoc' => $timestamp, + 'line' => $line, + 'metadata' => $metadata, + ]) . PHP_EOL, + FILE_APPEND | LOCK_EX); } + /** + * {@inheritdoc} + */ public function getConfiguration(): array { return $this->configuration; } + /** + * {@inheritdoc} + */ public function setConfiguration(array $configuration): static { $this->configuration = $configuration + $this->defaultConfiguration(); return $this; } + /** + * {@inheritdoc} + */ public function defaultConfiguration(): array { return [ - 'path' => '/tmp/os2forms_audit.log', + 'file' => '/tmp/os2forms_audit.log', ]; } + /** + * {@inheritdoc} + */ public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { - $form['path'] = [ + $form['file'] = [ '#type' => 'textfield', - '#title' => $this->t('Full path to the audit log file to store entries in'), - '#default_value' => $this->configuration['path'], + '#title' => $this->t('The complete path and name of the file where log entries are stored.'), + '#default_value' => $this->configuration['file'], ]; return $form; } + /** + * {@inheritdoc} + */ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { // @todo Implement validateConfigurationForm() method. } - public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void { if (!$form_state->getErrors()) { $values = $form_state->getValues(); $configuration = [ - 'path' => $values['path'], + 'file' => $values['file'], ]; $this->setConfiguration($configuration); } } + } diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php b/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php index 0f075c70..986a98e8 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php @@ -3,7 +3,6 @@ namespace Drupal\os2forms_audit\Plugin\AuditLogger; use Drupal\Component\Plugin\ConfigurableInterface; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Plugin\PluginFormInterface; @@ -27,27 +26,37 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition /** * {@inheritdoc} */ - public function write(EntityInterface $entity): void { - - // Then log the action like this: - \Drupal::logger('os2forms_audit')->notice('Entity with ID @id is written.', ['@id' => $entity->id()]); + public function log(int $timestamp, string $line, array $metadata = []): void { + // @todo use loki client to send message to loki } + /** + * {@inheritdoc} + */ public function getConfiguration(): array { return $this->configuration; } + /** + * {@inheritdoc} + */ public function setConfiguration(array $configuration): static { $this->configuration = $configuration + $this->defaultConfiguration(); return $this; } + /** + * {@inheritdoc} + */ public function defaultConfiguration(): array { return [ - 'entrypoint' => 'http://loki:3100' + 'entrypoint' => 'http://loki:3100', ]; } + /** + * {@inheritdoc} + */ public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { $form['entrypoint'] = [ '#type' => 'url', @@ -81,6 +90,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta return $form; } + /** + * {@inheritdoc} + */ public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void { $values = $form_state->getValues(); @@ -101,7 +113,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form $curlOptions = array_filter(explode(',', $values['curl_options'])); foreach ($curlOptions as $option) { - [$key,] = explode(' =>', $option); + [$key] = explode(' =>', $option); $key = trim($key); if (!defined($key)) { $form_state->setErrorByName('curl_options', $this->t('%option is not a valid cURL option.', ['%option' => $key])); @@ -110,6 +122,9 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form } } + /** + * {@inheritdoc} + */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void { if (!$form_state->getErrors()) { $values = $form_state->getValues(); @@ -117,7 +132,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s 'entrypoint' => $values['entrypoint'], 'auth' => [ 'username' => $values['auth']['username'], - 'password' => $values['auth']['password'] + 'password' => $values['auth']['password'], ], 'curl_options' => $values['curl_options'], ]; diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php b/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php index dac2cf68..eb3b9f3e 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php @@ -2,8 +2,6 @@ namespace Drupal\os2forms_audit\Plugin\AuditLogger; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginBase; /** @@ -20,10 +18,13 @@ class Watchdog extends PluginBase implements AuditLoggerInterface { /** * {@inheritdoc} */ - public function write(EntityInterface $entity): void { - // Code to write the $entity data to a file as an audit entry. + public function log(int $timestamp, string $line, array $metadata = []): void { + $data = ''; + array_walk($metadata, function ($val, $key) use (&$data) { + $data .= " $key=\"$val\""; + }); - // Then log the action like this: - \Drupal::logger('os2forms_audit')->info('Entity with ID @id is written.', ['@id' => $entity->id()]); + \Drupal::logger('os2forms_audit')->info($line . ' (%data)', ['data' => $data]); } + } diff --git a/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php b/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php index c1aa555f..b97ea5e0 100644 --- a/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php +++ b/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php @@ -2,9 +2,7 @@ namespace Drupal\os2forms_audit\Plugin\Derivative; -use Drupal\Component\Plugin\ConfigurableInterface; use Drupal\Component\Plugin\Derivative\DeriverBase; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Drupal\os2forms_audit\Plugin\LoggerManager; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -19,6 +17,9 @@ public function __construct( ) { } + /** + * {@inheritdoc} + */ public static function create(ContainerInterface $container, $base_plugin_id): LocalTask|static { return new static( $container->get('plugin.manager.os2forms_audit_logger') diff --git a/modules/os2forms_audit/src/Plugin/LoggerManager.php b/modules/os2forms_audit/src/Plugin/LoggerManager.php index 2a761e71..fea52376 100644 --- a/modules/os2forms_audit/src/Plugin/LoggerManager.php +++ b/modules/os2forms_audit/src/Plugin/LoggerManager.php @@ -2,12 +2,12 @@ namespace Drupal\os2forms_audit\Plugin; -use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\DefaultPluginManager; /** - * Provides the Fruit plugin manager. + * Provides the Logger plugin manager. * * @see \Drupal\os2forms_audit\Annotation\AuditLoggerProvider * @see \Drupal\os2forms_audit\Plugin\AuditLogger\AuditLoggerInterface @@ -16,10 +16,9 @@ class LoggerManager extends DefaultPluginManager { /** - * Constructor for FruitManager objects. + * Constructor for LoggerManager objects. */ public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { - parent::__construct( 'Plugin/AuditLogger', $namespaces, @@ -28,7 +27,6 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac 'Drupal\os2forms_audit\Annotation\AuditLoggerProvider', ); - $this->alterInfo('os2forms_audit_logger_info'); $this->setCacheBackend($cache_backend, 'os2forms_audit_logger_plugins'); } diff --git a/modules/os2forms_audit/src/Service/Logger.php b/modules/os2forms_audit/src/Service/Logger.php index bdc656e1..c4d9e940 100644 --- a/modules/os2forms_audit/src/Service/Logger.php +++ b/modules/os2forms_audit/src/Service/Logger.php @@ -2,22 +2,44 @@ namespace Drupal\os2forms_audit\Service; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\os2forms_audit\Form\PluginSettingsForm; +use Drupal\os2forms_audit\Form\SettingsForm; use Drupal\os2forms_audit\Plugin\LoggerManager; /** - * Class Logger + * Class Logger. * * Helper service to send log messages in the right direction. */ class Logger { public function __construct( - private readonly LoggerManager $loggerManager + private readonly LoggerManager $loggerManager, + private readonly ConfigFactoryInterface $configFactory ) { } - public function log(): void { - $this->loggerManager->getDefinitions(); + /** + * Logs a message using a plugin-specific logger. + * + * @param int $timestamp + * The timestamp for the log message. + * @param string $line + * The log message. + * @param array $metadata + * Additional metadata for the log message. Default is an empty array. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function log(int $timestamp, string $line, array $metadata = []): void { + $config = $this->configFactory->get(SettingsForm::$configName); + $plugin_id = $config->get('provider'); + $configuration = $this->configFactory->get(PluginSettingsForm::getConfigName())->get($plugin_id); + $logger = $this->loggerManager->createInstance($plugin_id, $configuration ?? []); + + $logger->log($timestamp, $line, $metadata); } + } diff --git a/modules/os2forms_audit/src/Service/LokiClient.php b/modules/os2forms_audit/src/Service/LokiClient.php index eea69bf4..fa6b5da5 100644 --- a/modules/os2forms_audit/src/Service/LokiClient.php +++ b/modules/os2forms_audit/src/Service/LokiClient.php @@ -10,6 +10,8 @@ class LokiClient { /** + * Location of the loki entry point. + * * @var string|null */ protected ?string $entrypoint; @@ -36,8 +38,8 @@ class LokiClient { private ?\CurlHandle $connection = NULL; /** - * Default constructor - * . + * Default constructor. + * * @param array $apiConfig * Configuration for the loki connection. */ @@ -55,31 +57,30 @@ public function __construct( /** * Send a log message to Loki ingestion endpoint. * - * Message format sendt to loki (https://grafana.com/docs/loki/latest/reference/api/#ingest-logs) - * in the following json format. + * Message format sendt to loki in the following json format. * { - * "Streams": [ - * { - * "stream": { - * "label": "value" - * }, - * "values": [ - * [ "", "", ] - * ] - * } - * ] + * "Streams": [ + * { + * "stream": { + * "label": "value" + * }, + * "values": [ + * [ "", "", ] + * ] + * } + * ] * } * * @param string $label * Loki global label to use. * @param int $epoch - * Unix epoch in nanoseconds + * Unix epoch in nanoseconds. * @param string $line * The log line to send. * @param array $metadata * Structured metadata. * - * @return void + * @see https://grafana.com/docs/loki/latest/reference/api/#ingest-logs * * @throws \JsonException */ @@ -97,7 +98,7 @@ public function send(string $label, int $epoch, string $line, array $metadata = } /** - * Ensure the URL to entry point is correct + * Ensure the URL to entry point is correct. * * @param string $entrypoint * Entry point URL. @@ -119,8 +120,6 @@ private function getEntrypoint(string $entrypoint): string { * @param array $packet * The packet to send. * - * @return void - * * @throws \JsonException * If unable to encode the packet to JSON. * @throws \LogicException From e327173c72847edad5dcb9a100da29f4918dfad5 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Tue, 23 Apr 2024 15:58:29 +0200 Subject: [PATCH 5/8] ITKDev: Fixed sending data into loki --- .../os2forms_audit.services.yml | 2 +- .../src/Commands/AuditLogDrushCommands.php | 4 +- .../src/Form/PluginSettingsForm.php | 4 +- .../os2forms_audit/src/Form/SettingsForm.php | 2 +- .../AuditLogger/AuditLoggerInterface.php | 4 +- .../src/Plugin/AuditLogger/File.php | 3 +- .../src/Plugin/AuditLogger/Loki.php | 27 ++++---- .../src/Plugin/AuditLogger/Watchdog.php | 8 ++- .../src/Plugin/Derivative/LocalTask.php | 2 +- modules/os2forms_audit/src/Service/Logger.php | 8 ++- .../os2forms_audit/src/Service/LokiClient.php | 69 +++++++++---------- .../src/Service/LokiClientInterface.php | 40 +++++++++++ 12 files changed, 108 insertions(+), 65 deletions(-) create mode 100644 modules/os2forms_audit/src/Service/LokiClientInterface.php diff --git a/modules/os2forms_audit/os2forms_audit.services.yml b/modules/os2forms_audit/os2forms_audit.services.yml index b6b589e9..a1e94294 100644 --- a/modules/os2forms_audit/os2forms_audit.services.yml +++ b/modules/os2forms_audit/os2forms_audit.services.yml @@ -8,7 +8,7 @@ services: arguments: ['@plugin.manager.os2forms_audit_logger', '@config.factory'] os2forms_audit.commands: - class: Drupal\os2forms_audit\Commands\Os2FormsAuditDrushCommands + class: Drupal\os2forms_audit\Commands\AuditLogDrushCommands arguments: ['@os2forms_audit.logger'] tags: - { name: drush.command } diff --git a/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php b/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php index 133569ab..b6af8064 100644 --- a/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php +++ b/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php @@ -17,7 +17,7 @@ class AuditLogDrushCommands extends DrushCommands { * Audit logger service. */ public function __construct( - protected readonly Logger $auditLogger + protected readonly Logger $auditLogger, ) { parent::__construct(); } @@ -35,7 +35,7 @@ public function __construct( * @throws \Drupal\Component\Plugin\Exception\PluginException */ public function logMessage(string $log_message = ''): void { - $this->auditLogger->log(time(), $log_message, []); + $this->auditLogger->log('test', time(), $log_message, ['from' => 'drush']); } } diff --git a/modules/os2forms_audit/src/Form/PluginSettingsForm.php b/modules/os2forms_audit/src/Form/PluginSettingsForm.php index f344ecf3..f2e535e7 100644 --- a/modules/os2forms_audit/src/Form/PluginSettingsForm.php +++ b/modules/os2forms_audit/src/Form/PluginSettingsForm.php @@ -30,8 +30,8 @@ class PluginSettingsForm extends ConfigFormBase implements PluginSettingsFormInt * {@inheritdoc} */ public function __construct( - ConfigFactoryInterface $config_factory, - PluginManagerInterface $manager + ConfigFactoryInterface $config_factory, + PluginManagerInterface $manager, ) { parent::__construct($config_factory); $this->manager = $manager; diff --git a/modules/os2forms_audit/src/Form/SettingsForm.php b/modules/os2forms_audit/src/Form/SettingsForm.php index de605dd2..7c7964ac 100644 --- a/modules/os2forms_audit/src/Form/SettingsForm.php +++ b/modules/os2forms_audit/src/Form/SettingsForm.php @@ -20,7 +20,7 @@ class SettingsForm extends ConfigFormBase { */ public function __construct( ConfigFactoryInterface $configFactory, - private readonly LoggerManager $loggerManager + private readonly LoggerManager $loggerManager, ) { parent::__construct($configFactory); } diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php b/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php index 579d0e56..ff3daedf 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/AuditLoggerInterface.php @@ -12,6 +12,8 @@ interface AuditLoggerInterface extends PluginInspectionInterface { /** * Logs a message with optional metadata. * + * @param string $type + * The type of event to log (auth, lookup etc.) * @param int $timestamp * The timestamp of the log entry. * @param string $line @@ -20,6 +22,6 @@ interface AuditLoggerInterface extends PluginInspectionInterface { * Additional metadata associated with the log entry. Defaults to an empty * array. */ - public function log(int $timestamp, string $line, array $metadata = []): void; + public function log(string $type, int $timestamp, string $line, array $metadata = []): void; } diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/File.php b/modules/os2forms_audit/src/Plugin/AuditLogger/File.php index c0227ffb..63cf96fe 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/File.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/File.php @@ -26,12 +26,13 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition /** * {@inheritdoc} */ - public function log(int $timestamp, string $line, array $metadata = []): void { + public function log(string $type, int $timestamp, string $line, array $metadata = []): void { // Code to write the entity to a file. // This is just a placeholder and won't write the data. file_put_contents( $this->configuration['file'], json_encode([ + 'type' => $type, 'epoc' => $timestamp, 'line' => $line, 'metadata' => $metadata, diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php b/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php index 986a98e8..6db8c908 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/Loki.php @@ -6,6 +6,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Plugin\PluginFormInterface; +use Drupal\os2forms_audit\Service\LokiClient; /** * Stores entities in the database. @@ -18,6 +19,9 @@ */ class Loki extends PluginBase implements AuditLoggerInterface, PluginFormInterface, ConfigurableInterface { + /** + * {@inheritdoc} + */ public function __construct(array $configuration, $plugin_id, $plugin_definition) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->setConfiguration($configuration); @@ -25,9 +29,16 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition /** * {@inheritdoc} + * + * @throws \JsonException */ - public function log(int $timestamp, string $line, array $metadata = []): void { - // @todo use loki client to send message to loki + public function log(string $type, int $timestamp, string $line, array $metadata = []): void { + $client = new LokiClient([ + 'entrypoint' => $this->configuration['entrypoint'], + 'auth' => $this->configuration['auth'], + ]); + // Convert timestamp to nanoseconds. + $client->send($type, $timestamp * 1000000000, $line, $metadata); } /** @@ -70,13 +81,11 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta 'username' => [ '#type' => 'textfield', '#title' => $this->t('Username'), - '#required' => TRUE, '#default_value' => $this->configuration['auth']['username'], ], 'password' => [ '#type' => 'password', '#title' => $this->t('Password'), - '#required' => TRUE, '#default_value' => $this->configuration['auth']['password'], ], ]; @@ -101,16 +110,6 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form $form_state->setErrorByName('entrypoint', $this->t('Invalid URL.')); } - // Validate auth username. - if (empty($values['auth']['username'])) { - $form_state->setErrorByName('auth][username', $this->t('Username is required.')); - } - - // Validate auth password. - if (empty($values['auth']['password'])) { - $form_state->setErrorByName('auth][password', $this->t('Password is required.')); - } - $curlOptions = array_filter(explode(',', $values['curl_options'])); foreach ($curlOptions as $option) { [$key] = explode(' =>', $option); diff --git a/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php b/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php index eb3b9f3e..f99e3a53 100644 --- a/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php +++ b/modules/os2forms_audit/src/Plugin/AuditLogger/Watchdog.php @@ -18,13 +18,17 @@ class Watchdog extends PluginBase implements AuditLoggerInterface { /** * {@inheritdoc} */ - public function log(int $timestamp, string $line, array $metadata = []): void { + public function log(string $type, int $timestamp, string $line, array $metadata = []): void { $data = ''; array_walk($metadata, function ($val, $key) use (&$data) { $data .= " $key=\"$val\""; }); - \Drupal::logger('os2forms_audit')->info($line . ' (%data)', ['data' => $data]); + \Drupal::logger('os2forms_audit')->info('%type: %line (%data)', [ + 'type' => $type, + 'line' => $line, + 'data' => $data, + ]); } } diff --git a/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php b/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php index b97ea5e0..855edd49 100644 --- a/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php +++ b/modules/os2forms_audit/src/Plugin/Derivative/LocalTask.php @@ -13,7 +13,7 @@ class LocalTask extends DeriverBase implements ContainerDeriverInterface { public function __construct( - private readonly LoggerManager $loggerManager + private readonly LoggerManager $loggerManager, ) { } diff --git a/modules/os2forms_audit/src/Service/Logger.php b/modules/os2forms_audit/src/Service/Logger.php index c4d9e940..ca0675e1 100644 --- a/modules/os2forms_audit/src/Service/Logger.php +++ b/modules/os2forms_audit/src/Service/Logger.php @@ -16,13 +16,15 @@ class Logger { public function __construct( private readonly LoggerManager $loggerManager, - private readonly ConfigFactoryInterface $configFactory + private readonly ConfigFactoryInterface $configFactory, ) { } /** * Logs a message using a plugin-specific logger. * + * @param string $type + * The type of event to log (auth, lookup etc.) * @param int $timestamp * The timestamp for the log message. * @param string $line @@ -32,14 +34,14 @@ public function __construct( * * @throws \Drupal\Component\Plugin\Exception\PluginException */ - public function log(int $timestamp, string $line, array $metadata = []): void { + public function log(string $type, int $timestamp, string $line, array $metadata = []): void { $config = $this->configFactory->get(SettingsForm::$configName); $plugin_id = $config->get('provider'); $configuration = $this->configFactory->get(PluginSettingsForm::getConfigName())->get($plugin_id); $logger = $this->loggerManager->createInstance($plugin_id, $configuration ?? []); - $logger->log($timestamp, $line, $metadata); + $logger->log($type, $timestamp, $line, $metadata); } } diff --git a/modules/os2forms_audit/src/Service/LokiClient.php b/modules/os2forms_audit/src/Service/LokiClient.php index fa6b5da5..6031624a 100644 --- a/modules/os2forms_audit/src/Service/LokiClient.php +++ b/modules/os2forms_audit/src/Service/LokiClient.php @@ -7,7 +7,7 @@ * * This is based/inspired by https://github.com/itspire/monolog-loki. */ -class LokiClient { +class LokiClient implements LokiClientInterface { /** * Location of the loki entry point. @@ -49,52 +49,36 @@ public function __construct( $this->entrypoint = $this->getEntrypoint($apiConfig['entrypoint']); $this->customCurlOptions = $apiConfig['curl_options'] ?? []; - if (isset($apiConfig['auth']['basic'])) { - $this->basicAuth = (2 === count($apiConfig['auth']['basic'])) ? $apiConfig['auth']['basic'] : []; + if (isset($apiConfig['auth']) && !empty($apiConfig['auth']['username']) && !empty($apiConfig['auth']['password'])) { + $this->basicAuth = $apiConfig['auth']; } } /** - * Send a log message to Loki ingestion endpoint. - * - * Message format sendt to loki in the following json format. - * { - * "Streams": [ - * { - * "stream": { - * "label": "value" - * }, - * "values": [ - * [ "", "", ] - * ] - * } - * ] - * } - * - * @param string $label - * Loki global label to use. - * @param int $epoch - * Unix epoch in nanoseconds. - * @param string $line - * The log line to send. - * @param array $metadata - * Structured metadata. - * - * @see https://grafana.com/docs/loki/latest/reference/api/#ingest-logs + * {@inheritdoc} * * @throws \JsonException */ public function send(string $label, int $epoch, string $line, array $metadata = []): void { - $this->sendPacket([ + $packet = [ 'streams' => [ - 'stream' => [ - 'label' => $label, - ], - 'values' => [ - [$epoch, $line, $metadata], + [ + 'stream' => [ + 'app' => 'os2forms', + 'type' => $label, + ], + 'values' => [ + [(string) $epoch, $line], + ], ], ], - ]); + ]; + + if (!empty($metadata)) { + $packet['streams'][0]['stream'] += $metadata; + } + + $this->sendPacket($packet); } /** @@ -159,7 +143,18 @@ private function sendPacket(array $packet): void { } curl_setopt_array($this->connection, $curlOptions); - curl_exec($this->connection); + $result = curl_exec($this->connection); + + if (FALSE === $result) { + throw new \RuntimeException('Error sending packet to Loki'); + } + + if (curl_errno($this->connection)) { + echo 'Curl error: ' . curl_error($this->connection); + } + else { + echo 'Curl result: ' . $result; + } } } diff --git a/modules/os2forms_audit/src/Service/LokiClientInterface.php b/modules/os2forms_audit/src/Service/LokiClientInterface.php new file mode 100644 index 00000000..eb2b269e --- /dev/null +++ b/modules/os2forms_audit/src/Service/LokiClientInterface.php @@ -0,0 +1,40 @@ +", ""] + * ] + * } + * ] + * } + * + * @param string $label + * Loki global label to use. + * @param int $epoch + * Unix epoch in nanoseconds. + * @param string $line + * The log line to send. + * @param array $metadata + * Extra labels/metadata to filter on. + * + * @see https://grafana.com/docs/loki/latest/reference/api/#ingest-logs + */ + public function send(string $label, int $epoch, string $line, array $metadata = []): void; + +} From c60de6903ac086da3393848987c82d76f55106e6 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 24 Apr 2024 11:15:55 +0200 Subject: [PATCH 6/8] ITKDev: Added wrapper function for audit logging --- .../src/Commands/AuditLogDrushCommands.php | 3 +++ .../src/Form/PluginSettingsForm.php | 2 +- .../os2forms_audit/src/Form/SettingsForm.php | 2 +- modules/os2forms_audit/src/Service/Logger.php | 2 ++ os2forms.module | 25 +++++++++++++++++++ 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php b/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php index b6af8064..3ba4769c 100644 --- a/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php +++ b/modules/os2forms_audit/src/Commands/AuditLogDrushCommands.php @@ -35,6 +35,9 @@ public function __construct( * @throws \Drupal\Component\Plugin\Exception\PluginException */ public function logMessage(string $log_message = ''): void { + if (empty($log_message)) { + throw new \Exception('Log message cannot be empty.'); + } $this->auditLogger->log('test', time(), $log_message, ['from' => 'drush']); } diff --git a/modules/os2forms_audit/src/Form/PluginSettingsForm.php b/modules/os2forms_audit/src/Form/PluginSettingsForm.php index f2e535e7..ea88d066 100644 --- a/modules/os2forms_audit/src/Form/PluginSettingsForm.php +++ b/modules/os2forms_audit/src/Form/PluginSettingsForm.php @@ -40,7 +40,7 @@ public function __construct( /** * {@inheritdoc} */ - public static function create(ContainerInterface $container): PluginSettingsForm|ConfigFormBase|static { + public static function create(ContainerInterface $container): static { return new static( $container->get('config.factory'), $container->get('plugin.manager.os2forms_audit_logger') diff --git a/modules/os2forms_audit/src/Form/SettingsForm.php b/modules/os2forms_audit/src/Form/SettingsForm.php index 7c7964ac..9e3e68e2 100644 --- a/modules/os2forms_audit/src/Form/SettingsForm.php +++ b/modules/os2forms_audit/src/Form/SettingsForm.php @@ -28,7 +28,7 @@ public function __construct( /** * {@inheritdoc} */ - public static function create(ContainerInterface $container) { + public static function create(ContainerInterface $container): static { return new static( $container->get('config.factory'), $container->get('plugin.manager.os2forms_audit_logger') diff --git a/modules/os2forms_audit/src/Service/Logger.php b/modules/os2forms_audit/src/Service/Logger.php index ca0675e1..a11ab72d 100644 --- a/modules/os2forms_audit/src/Service/Logger.php +++ b/modules/os2forms_audit/src/Service/Logger.php @@ -38,6 +38,8 @@ public function log(string $type, int $timestamp, string $line, array $metadata $config = $this->configFactory->get(SettingsForm::$configName); $plugin_id = $config->get('provider'); + // @todo: default logger (file) + // @todo: Fallback logger on error. $configuration = $this->configFactory->get(PluginSettingsForm::getConfigName())->get($plugin_id); $logger = $this->loggerManager->createInstance($plugin_id, $configuration ?? []); diff --git a/os2forms.module b/os2forms.module index 1357b459..8f22ac25 100644 --- a/os2forms.module +++ b/os2forms.module @@ -357,3 +357,28 @@ function os2forms_module_implements_alter(&$implementations, $hook) { break; } } + +/** + * Logging function for audit logs. + * + * Wrapper to make logging easier and simplify the usage of the audit logger, if + * enabled. + * + * @param string $type + * The type of log entry. + * @param int $timestamp + * The timestamp of the log entry. + * @param string $line + * The log message. + * @param array $metadata + * Additional metadata for the log entry. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ +function os2forms_audit_log(string $type, int $timestamp, string $line, array $metadata = []): void { + if ( \Drupal::service('module_handler')->moduleExists('os2form_audit')) { + /** @var \Drupal\os2forms_audit\Service\Logger $logger */ + $logger = \Drupal::service('os2form_audit.logger'); + $logger->log($type, $timestamp, $line, $metadata); + } +} From f7ff39e1ff64674081f99765627b20b7b1ed75df Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 24 Apr 2024 15:17:57 +0200 Subject: [PATCH 7/8] ITKDev: Added user id to logging helper function --- .../Element/WebformAttachmentConsentXml.php | 2 +- os2forms.module | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/modules/os2forms_consent/src/Element/WebformAttachmentConsentXml.php b/modules/os2forms_consent/src/Element/WebformAttachmentConsentXml.php index 9f7eaa6f..35e81977 100644 --- a/modules/os2forms_consent/src/Element/WebformAttachmentConsentXml.php +++ b/modules/os2forms_consent/src/Element/WebformAttachmentConsentXml.php @@ -45,7 +45,7 @@ public static function getFileContent(array $element, WebformSubmissionInterface /** @var \Drupal\os2web_datalookup\Plugin\DataLookupManager $os2web_datalookup_plugins */ $os2web_datalookup_plugins = \Drupal::service('plugin.manager.os2web_datalookup'); - /** @var \Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCPRInterface $cprPlugin */ + /** @var \Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupInterfaceCpr $cprPlugin */ $cprPlugin = $os2web_datalookup_plugins->createDefaultInstanceByGroup('cpr_lookup'); if (!empty($nemid_cpr) && $cprPlugin->isReady()) { diff --git a/os2forms.module b/os2forms.module index 8f22ac25..d3751ba8 100644 --- a/os2forms.module +++ b/os2forms.module @@ -370,15 +370,27 @@ function os2forms_module_implements_alter(&$implementations, $hook) { * The timestamp of the log entry. * @param string $line * The log message. + * @param bool $logUser + * Log information about the current logged-in user (need to track who has + * lookup information in external services). Default: false. * @param array $metadata * Additional metadata for the log entry. - * - * @throws \Drupal\Component\Plugin\Exception\PluginException */ -function os2forms_audit_log(string $type, int $timestamp, string $line, array $metadata = []): void { +function os2forms_audit_log(string $type, int $timestamp, string $line, bool $logUser = false, array $metadata = []): void { if ( \Drupal::service('module_handler')->moduleExists('os2form_audit')) { + if ($logUser) { + // Add user id to the log message. + $user = \Drupal::currentUser(); + $metadata['userId'] = $user->id(); + } + /** @var \Drupal\os2forms_audit\Service\Logger $logger */ $logger = \Drupal::service('os2form_audit.logger'); - $logger->log($type, $timestamp, $line, $metadata); + try { + $logger->log($type, $timestamp, $line, $metadata); + } + catch (\Drupal\Component\Plugin\Exception\PluginException $e) { + // @todo: what should we do when plugins fails. + } } } From 158ea5f092e0a521b6a23cbeb2db8838784eea4d Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 24 Apr 2024 16:05:36 +0200 Subject: [PATCH 8/8] ITKDev: Updated code style and doc blocks in os2forms_digital_post --- .../Commands/DigitalPostTestCommands.php | 8 +++++ .../src/Form/SettingsForm.php | 32 +++++++++-------- .../src/Helper/AbstractMessageHelper.php | 5 ++- .../src/Helper/BeskedfordelerHelper.php | 12 +++++-- .../src/Helper/CertificateLocatorHelper.php | 8 ++++- .../src/Helper/DigitalPostHelper.php | 15 ++++++++ .../src/Helper/ForsendelseHelper.php | 13 +++++-- .../src/Helper/MeMoHelper.php | 2 +- .../src/Helper/Settings.php | 12 +++++-- .../src/Helper/WebformHelperSF1601.php | 4 +++ .../WebformHandler/WebformHandlerSF1601.php | 35 ++++++++++--------- 11 files changed, 105 insertions(+), 41 deletions(-) diff --git a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php index 8db7f4f1..af320589 100644 --- a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php +++ b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php @@ -52,6 +52,12 @@ public function __construct( * * @command os2forms-digital-post:test:send * @usage os2forms-digital-post:test:send --help + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface */ public function send( array $recipients, @@ -138,6 +144,8 @@ public function send( /** * Dump digital post settings. + * + * @throws \Drupal\os2forms_digital_post\Exception\InvalidSettingException */ private function dumpDigitalPostSettings(SymfonyStyle $io): void { $io->section('Digital post settings'); diff --git a/modules/os2forms_digital_post/src/Form/SettingsForm.php b/modules/os2forms_digital_post/src/Form/SettingsForm.php index ad0fd962..1298ac98 100644 --- a/modules/os2forms_digital_post/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_post/src/Form/SettingsForm.php @@ -40,9 +40,9 @@ public function __construct( /** * {@inheritdoc} * - * @phpstan-return self + * @phpstan-return static */ - public static function create(ContainerInterface $container) { + public static function create(ContainerInterface $container): SettingsForm { return new static( $container->get(Settings::class), $container->get(CertificateLocatorHelper::class), @@ -53,7 +53,7 @@ public static function create(ContainerInterface $container) { /** * {@inheritdoc} */ - public function getFormId() { + public function getFormId(): string { return 'os2forms_digital_post_settings'; } @@ -62,8 +62,10 @@ public function getFormId() { * * @phpstan-param array $form * @phpstan-return array + * + * @throws \Drupal\os2forms_digital_post\Exception\InvalidSettingException */ - public function buildForm(array $form, FormStateInterface $form_state) { + public function buildForm(array $form, FormStateInterface $form_state): array { $form['test_mode'] = [ '#type' => 'checkbox', '#title' => $this->t('Test mode'), @@ -212,17 +214,17 @@ public function buildForm(array $form, FormStateInterface $form_state) { * * @phpstan-param array $form */ - public function validateForm(array &$form, FormStateInterface $formState): void { - $triggeringElement = $formState->getTriggeringElement(); + public function validateForm(array &$form, FormStateInterface $form_state): void { + $triggeringElement = $form_state->getTriggeringElement(); if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { return; } - $values = $formState->getValues(); + $values = $form_state->getValues(); if (CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM === $values['certificate']['locator_type']) { $path = $values['certificate'][CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]['path'] ?? NULL; if (!file_exists($path)) { - $formState->setErrorByName('certificate][file_system][path', $this->t('Invalid certificate path: %path', ['%path' => $path])); + $form_state->setErrorByName('certificate][file_system][path', $this->t('Invalid certificate path: %path', ['%path' => $path])); } } } @@ -232,18 +234,18 @@ public function validateForm(array &$form, FormStateInterface $formState): void * * @phpstan-param array $form */ - public function submitForm(array &$form, FormStateInterface $formState): void { - $triggeringElement = $formState->getTriggeringElement(); + public function submitForm(array &$form, FormStateInterface $form_state): void { + $triggeringElement = $form_state->getTriggeringElement(); if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { $this->testCertificate(); return; } try { - $settings['test_mode'] = (bool) $formState->getValue('test_mode'); - $settings['sender'] = $formState->getValue('sender'); - $settings['certificate'] = $formState->getValue('certificate'); - $settings['processing'] = $formState->getValue('processing'); + $settings['test_mode'] = (bool) $form_state->getValue('test_mode'); + $settings['sender'] = $form_state->getValue('sender'); + $settings['certificate'] = $form_state->getValue('certificate'); + $settings['processing'] = $form_state->getValue('processing'); $this->settings->setSettings($settings); $this->messenger()->addStatus($this->t('Settings saved')); } @@ -259,7 +261,7 @@ private function testCertificate(): void { try { $certificateLocator = $this->certificateLocatorHelper->getCertificateLocator(); $certificateLocator->getCertificates(); - $this->messenger()->addStatus($this->t('Certificate succesfully tested')); + $this->messenger()->addStatus($this->t('Certificate successfully tested')); } catch (\Throwable $throwable) { $message = $this->t('Error testing certificate: %message', ['%message' => $throwable->getMessage()]); diff --git a/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php b/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php index d9fb96a3..3af744c9 100644 --- a/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php +++ b/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php @@ -29,7 +29,10 @@ public function __construct( } /** - * Get main document. + * Get the main document. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Drupal\os2forms_digital_post\Exception\InvalidAttachmentElementException * * @see WebformAttachmentController::download() * diff --git a/modules/os2forms_digital_post/src/Helper/BeskedfordelerHelper.php b/modules/os2forms_digital_post/src/Helper/BeskedfordelerHelper.php index 5b93da5e..78024593 100644 --- a/modules/os2forms_digital_post/src/Helper/BeskedfordelerHelper.php +++ b/modules/os2forms_digital_post/src/Helper/BeskedfordelerHelper.php @@ -30,7 +30,9 @@ public function __construct( } /** - * Save MeMo message in database. + * Save MeMo message to the database. + * + * @throws \Exception */ public function createMessage(int $submissionId, MeMoMessage $message, string $receipt): int { $messageUUID = $message->getMessageHeader()->getMessageUUID(); @@ -51,11 +53,13 @@ public function createMessage(int $submissionId, MeMoMessage $message, string $r * Load message. * * @param string $messageUUID - * The message UUID. + * The message's UUID. * * @return \Drupal\os2forms_digital_post\Model\Message|null * The message. * + * @throws \Exception + * * @see Message::__set() */ public function loadMessage(string $messageUUID): ?Message { @@ -70,6 +74,8 @@ public function loadMessage(string $messageUUID): ?Message { /** * Add Beskedfordeler message to message. + * + * @throws \Exception */ public function addBeskedfordelerMessage(string $messageUUID, string $beskedfordelerMessage): bool { $message = $this->loadMessage($messageUUID); @@ -92,6 +98,8 @@ public function addBeskedfordelerMessage(string $messageUUID, string $beskedford * * @param array|WebformSubmissionInterface[] $submissions * The submissions. + * + * @throws \Exception */ public function deleteMessages(array $submissions): void { $submissionIds = array_map(static function (WebformSubmissionInterface $submission) { diff --git a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php index 7c4d4117..ce230a4a 100644 --- a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php +++ b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php @@ -20,7 +20,10 @@ class CertificateLocatorHelper { public const LOCATOR_TYPE_FILE_SYSTEM = 'file_system'; /** - * {@inheritdoc} + * Default constructor. + * + * @param Settings $settings + * Modul configuration. */ public function __construct( private readonly Settings $settings, @@ -29,6 +32,9 @@ public function __construct( /** * Get certificate locator. + * + * @throws \ItkDev\AzureKeyVault\Exception\TokenException + * @throws \Drupal\os2forms_digital_post\Exception\CertificateLocatorException */ public function getCertificateLocator(): CertificateLocatorInterface { $certificateSettings = $this->settings->getCertificate(); diff --git a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php index c51f0dee..828ddeae 100644 --- a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php +++ b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php @@ -53,6 +53,13 @@ public function __construct( * @return array * [The response, The kombi post message]. * + * @throws \Drupal\os2forms_digital_post\Exception\CertificateLocatorException + * @throws \ItkDev\AzureKeyVault\Exception\TokenException + * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface + * * @phpstan-return array */ public function sendDigitalPost(string $type, Message $message, ?ForsendelseI $forsendelse, WebformSubmissionInterface $submission = NULL): array { @@ -87,6 +94,9 @@ public function log($level, $message, array $context = []): void { /** * Look up CPR. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Drupal\os2forms_digital_post\Exception\RuntimeException */ public function lookupCpr(string $cpr): CprLookupResult { $instance = $this->dataLookupManager->createDefaultInstanceByGroup('cpr_lookup'); @@ -103,6 +113,9 @@ public function lookupCpr(string $cpr): CprLookupResult { /** * Look up CVR. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Drupal\os2forms_digital_post\Exception\RuntimeException */ public function lookupCvr(string $cvr): CompanyLookupResult { $instance = $this->dataLookupManager->createDefaultInstanceByGroup('cvr_lookup'); @@ -119,6 +132,8 @@ public function lookupCvr(string $cvr): CompanyLookupResult { /** * Look up recipient. + * + * @throws \Drupal\os2forms_digital_post\Exception\RuntimeException */ public function lookupRecipient(string $recipient): CprLookupResult|CompanyLookupResult { try { diff --git a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php index 42cbbea1..934b38ce 100644 --- a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php +++ b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php @@ -26,6 +26,8 @@ class ForsendelseHelper extends AbstractMessageHelper { /** * Build forsendelse. + * + * @throws \Drupal\os2forms_digital_post\Exception\InvalidForsendelseException */ public function buildForsendelse(CprLookupResult|CompanyLookupResult $recipientData, string $messageLabel, Document $document): ForsendelseI { $forsendelse = new ForsendelseI(); @@ -56,8 +58,15 @@ public function buildForsendelse(CprLookupResult|CompanyLookupResult $recipientD /** * Build forsendelse form a webform submission. * - * @phpstan-param array $options - * @phpstan-param array $handlerSettings + * @param WebformSubmissionInterface $submission + * @param array $options + * @param array $handlerSettings + * @param CprLookupResult|CompanyLookupResult $recipientData + * + * @return \Oio\Fjernprint\ForsendelseI + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Drupal\os2forms_digital_post\Exception\InvalidAttachmentElementException + * @throws \Drupal\os2forms_digital_post\Exception\InvalidForsendelseException */ public function buildSubmissionForsendelse(WebformSubmissionInterface $submission, array $options, array $handlerSettings, CprLookupResult|CompanyLookupResult $recipientData): ForsendelseI { $label = $this->replaceTokens($options[WebformHandlerSF1601::MESSAGE_HEADER_LABEL], $submission); diff --git a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php index 769dcad9..6911e2f2 100644 --- a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php +++ b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php @@ -110,7 +110,7 @@ public function buildWebformSubmissionMessage(WebformSubmissionInterface $submis } /** - * Enrich recipient with additional data from a lookup. + * Enrich the recipient with additional data from a lookup. */ private function enrichRecipient(Recipient $recipient, CprLookupResult|CompanyLookupResult|null $recipientData): Recipient { if ($recipientData instanceof CprLookupResult) { diff --git a/modules/os2forms_digital_post/src/Helper/Settings.php b/modules/os2forms_digital_post/src/Helper/Settings.php index e64be738..86a3fe4f 100644 --- a/modules/os2forms_digital_post/src/Helper/Settings.php +++ b/modules/os2forms_digital_post/src/Helper/Settings.php @@ -27,7 +27,7 @@ final class Settings { * * @var string */ - private $collection = 'os2forms_digital_post.'; + private string $collection = 'os2forms_digital_post.'; /** * Constructor. @@ -38,6 +38,8 @@ public function __construct(KeyValueFactoryInterface $keyValueFactory) { /** * Get test mode. + * + * @throws \Drupal\os2forms_digital_post\Exception\InvalidSettingException */ public function getTestMode(): bool { return (bool) $this->get('test_mode', TRUE); @@ -47,6 +49,8 @@ public function getTestMode(): bool { * Get sender. * * @phpstan-return array + * + * @throws \Drupal\os2forms_digital_post\Exception\InvalidSettingException */ public function getSender(): array { $value = $this->get('sender'); @@ -57,6 +61,8 @@ public function getSender(): array { * Get certificate. * * @phpstan-return array + * + * @throws \Drupal\os2forms_digital_post\Exception\InvalidSettingException */ public function getCertificate(): array { $value = $this->get('certificate'); @@ -83,8 +89,10 @@ public function getProcessing(): array { * * @return mixed * The setting value. + * + * @throws \Drupal\os2forms_digital_post\Exception\InvalidSettingException */ - private function get(string $key, $default = NULL) { + private function get(string $key, mixed $default = NULL) { $resolver = $this->getSettingsResolver(); if (!$resolver->isDefined($key)) { throw new InvalidSettingException(sprintf('Setting %s is not defined', $key)); diff --git a/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php b/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php index f3054f8b..d1121552 100644 --- a/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php +++ b/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php @@ -79,6 +79,10 @@ public function __construct( * @phpstan-param array $handlerSettings * @phpstan-param array $submissionData * @phpstan-return array + * + * @throws \Drupal\os2forms_digital_post\Exception\InvalidRecipientIdentifierElementException + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Drupal\os2forms_digital_post\Exception\RuntimeException */ public function sendDigitalPost(WebformSubmissionInterface $submission, array $handlerSettings, array $submissionData = []): array { $submissionData = $submissionData + $submission->getData(); diff --git a/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php b/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php index 9da57179..cd9ef796 100644 --- a/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php +++ b/modules/os2forms_digital_post/src/Plugin/WebformHandler/WebformHandlerSF1601.php @@ -74,7 +74,7 @@ public static function create(ContainerInterface $container, array $configuratio * * @phpstan-return array */ - public function defaultConfiguration() { + public function defaultConfiguration(): array { return [ 'debug' => FALSE, ]; @@ -86,7 +86,7 @@ public function defaultConfiguration() { * @phpstan-param array $form * @phpstan-return array */ - public function buildConfigurationForm(array $form, FormStateInterface $formState) { + public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { $form[self::MEMO_MESSAGE] = [ '#type' => 'fieldset', '#title' => $this->t('Message'), @@ -158,6 +158,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $formStat SF1601::ACTION_TILMELDING => $this->getTranslatedActionName(SF1601::ACTION_TILMELDING), SF1601::ACTION_UNDERSKRIV => $this->getTranslatedActionName(SF1601::ACTION_UNDERSKRIV), ]; + $actions = $this->configuration[self::MEMO_ACTIONS]['actions'] ?? []; for ($i = 0; $i <= count($actions); $i++) { $action = $actions[$i]; @@ -267,14 +268,14 @@ static function (array $element) use ($elementTypes) { * @phpstan-param array $form * @phpstan-return void */ - public function validateConfigurationForm(array &$form, FormStateInterface $formState) { - $actions = $formState->getValue(self::MEMO_ACTIONS)['actions'] ?? []; + public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void { + $actions = $form_state->getValue(self::MEMO_ACTIONS)['actions'] ?? []; $definedActions = []; foreach ($actions as $index => $action) { if (!empty($action['action'])) { if (empty($action['url'])) { - $formState->setErrorByName( + $form_state->setErrorByName( self::MEMO_ACTIONS . '][actions][' . $index . '][url', $this->t('Url for action %action is required.', [ '%action' => $this->getTranslatedActionName($action['action']), @@ -283,7 +284,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form ); } if (isset($definedActions[$action['action']])) { - $formState->setErrorByName( + $form_state->setErrorByName( self::MEMO_ACTIONS . '][actions][' . $index . '][action', $this->t('Action %action already defined.', [ '%action' => $this->getTranslatedActionName($action['action']), @@ -301,12 +302,12 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form * @phpstan-param array $form * @phpstan-return void */ - public function submitConfigurationForm(array &$form, FormStateInterface $formState) { - parent::submitConfigurationForm($form, $formState); + public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void { + parent::submitConfigurationForm($form, $form_state); - $this->configuration[self::MEMO_MESSAGE] = $formState->getValue(self::MEMO_MESSAGE); + $this->configuration[self::MEMO_MESSAGE] = $form_state->getValue(self::MEMO_MESSAGE); // Filter out actions with no action set. - $actions = $formState->getValue(self::MEMO_ACTIONS); + $actions = $form_state->getValue(self::MEMO_ACTIONS); $actions['actions'] = array_values(array_filter( $actions['actions'], static function (array $action) { @@ -315,7 +316,7 @@ static function (array $action) { )); $this->configuration[self::MEMO_ACTIONS] = $actions; - $this->configuration['debug'] = (bool) $formState->getValue('debug'); + $this->configuration['debug'] = (bool) $form_state->getValue('debug'); } /** @@ -323,8 +324,8 @@ static function (array $action) { * * @phpstan-return void */ - public function postSave(WebformSubmissionInterface $webformSubmission, $update = TRUE) { - $this->helper->createJob($webformSubmission, $this->configuration); + public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE): void { + $this->helper->createJob($webform_submission, $this->configuration); } /** @@ -332,8 +333,8 @@ public function postSave(WebformSubmissionInterface $webformSubmission, $update * * @phpstan-return void */ - public function postDelete(WebformSubmissionInterface $webformSubmission) { - $this->helper->deleteMessages([$webformSubmission]); + public function postDelete(WebformSubmissionInterface $webform_submission): void { + $this->helper->deleteMessages([$webform_submission]); } /** @@ -341,8 +342,8 @@ public function postDelete(WebformSubmissionInterface $webformSubmission) { * * @phpstan-return void */ - public function postPurge(array $webformSubmissions) { - $this->helper->deleteMessages($webformSubmissions); + public function postPurge(array $webform_submissions): void { + $this->helper->deleteMessages($webform_submissions); } /**