From 488a30a10612f3780056e980372df743dc1ece87 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Thu, 25 Apr 2024 11:00:06 +0200 Subject: [PATCH 01/16] ITKDev: Started on audit log module --- README.md | 10 +- os2web_audit.info.yml | 4 + os2web_audit.links.menu.yml | 5 + os2web_audit.links.task.yml | 5 + os2web_audit.module | 6 + os2web_audit.routing.yml | 8 + os2web_audit.services.yml | 14 ++ src/Annotation/AuditLoggerProvider.php | 44 +++++ src/Commands/AuditLogDrushCommands.php | 44 +++++ src/Controller/LocalTasksController.php | 58 +++++++ src/Form/PluginSettingsForm.php | 123 ++++++++++++++ src/Form/SettingsForm.php | 104 ++++++++++++ .../AuditLogger/AuditLoggerInterface.php | 27 +++ src/Plugin/AuditLogger/File.php | 101 +++++++++++ src/Plugin/AuditLogger/Loki.php | 142 ++++++++++++++++ src/Plugin/AuditLogger/Watchdog.php | 34 ++++ src/Plugin/Derivative/LocalTask.php | 64 +++++++ src/Plugin/LoggerManager.php | 34 ++++ src/Service/Logger.php | 49 ++++++ src/Service/LokiClient.php | 160 ++++++++++++++++++ src/Service/LokiClientInterface.php | 40 +++++ 21 files changed, 1071 insertions(+), 5 deletions(-) create mode 100644 os2web_audit.info.yml create mode 100644 os2web_audit.links.menu.yml create mode 100644 os2web_audit.links.task.yml create mode 100644 os2web_audit.module create mode 100644 os2web_audit.routing.yml create mode 100644 os2web_audit.services.yml create mode 100644 src/Annotation/AuditLoggerProvider.php create mode 100644 src/Commands/AuditLogDrushCommands.php create mode 100644 src/Controller/LocalTasksController.php create mode 100644 src/Form/PluginSettingsForm.php create mode 100644 src/Form/SettingsForm.php create mode 100644 src/Plugin/AuditLogger/AuditLoggerInterface.php create mode 100644 src/Plugin/AuditLogger/File.php create mode 100644 src/Plugin/AuditLogger/Loki.php create mode 100644 src/Plugin/AuditLogger/Watchdog.php create mode 100644 src/Plugin/Derivative/LocalTask.php create mode 100644 src/Plugin/LoggerManager.php create mode 100644 src/Service/Logger.php create mode 100644 src/Service/LokiClient.php create mode 100644 src/Service/LokiClientInterface.php diff --git a/README.md b/README.md index 4607955..c47ad4d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# OS2Form Audit Module +# OS2Web Audit Module -OS2Form Audit is a Drupal module that helps track changes and perform audit on -OS2Form events. +OS2Web Audit is a Drupal module that helps track changes and perform audit on +OS2Web events. ## Requirements - @@ -12,7 +12,7 @@ OS2Form events. ## Features - - Detailed audit log entries. -- Support for all OS2Form entity types. +- Support for all OS2Web entity types. - Easily extendable for custom use case. ## Installation @@ -28,7 +28,7 @@ extent page (`/admin/modules`). ## Configuration -Navigate to `/admin/config/os2form/audit` to configure the module. +Navigate to `/admin/config/audit` to configure the module. Some additional (brief) information on Usage, Extending/Overriding, and Maintainers could go here. diff --git a/os2web_audit.info.yml b/os2web_audit.info.yml new file mode 100644 index 0000000..b0c7153 --- /dev/null +++ b/os2web_audit.info.yml @@ -0,0 +1,4 @@ +name: OS2web Audit +type: module +description: 'OS2web Audit Module (log all events to external service)' +core_version_requirement: ^8 || ^9 || ^10 diff --git a/os2web_audit.links.menu.yml b/os2web_audit.links.menu.yml new file mode 100644 index 0000000..574ad72 --- /dev/null +++ b/os2web_audit.links.menu.yml @@ -0,0 +1,5 @@ +os2web_audit.admin_settings: + title: 'OS2web Audit settings' + parent: system.admin_config_system + description: 'Settings for the OS2web Audit module' + route_name: os2web_audit.plugin_settings_local_tasks diff --git a/os2web_audit.links.task.yml b/os2web_audit.links.task.yml new file mode 100644 index 0000000..b2303c7 --- /dev/null +++ b/os2web_audit.links.task.yml @@ -0,0 +1,5 @@ +os2web_audit.plugin_settings_tasks: + title: 'Dynamic tasks' + route_name: os2web_audit.plugin_settings_local_tasks + base_route: os2web_audit.plugin_settings_local_tasks + deriver: Drupal\os2web_audit\Plugin\Derivative\LocalTask diff --git a/os2web_audit.module b/os2web_audit.module new file mode 100644 index 0000000..29c53cf --- /dev/null +++ b/os2web_audit.module @@ -0,0 +1,6 @@ +auditLogger->log('test', time(), $log_message, ['from' => 'drush']); + } + +} diff --git a/src/Controller/LocalTasksController.php b/src/Controller/LocalTasksController.php new file mode 100644 index 0000000..c6bcd67 --- /dev/null +++ b/src/Controller/LocalTasksController.php @@ -0,0 +1,58 @@ +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 + * 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\os2web_audit\Form\SettingsForm'); + } + + return $this->formBuilder->getForm('\Drupal\os2web_audit\Form\PluginSettingsForm', $type); + } + +} diff --git a/src/Form/PluginSettingsForm.php b/src/Form/PluginSettingsForm.php new file mode 100644 index 0000000..aef8709 --- /dev/null +++ b/src/Form/PluginSettingsForm.php @@ -0,0 +1,123 @@ +manager = $manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): static { + return new static( + $container->get('config.factory'), + $container->get('plugin.manager.os2web_audit_logger') + ); + } + + /** + * {@inheritdoc} + */ + public static function getConfigName(): string { + return 'os2web_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/src/Form/SettingsForm.php b/src/Form/SettingsForm.php new file mode 100644 index 0000000..0f80994 --- /dev/null +++ b/src/Form/SettingsForm.php @@ -0,0 +1,104 @@ +get('config.factory'), + $container->get('plugin.manager.os2web_audit_logger') + ); + } + + /** + * The name of the configuration setting. + * + * @var string + */ + public static string $configName = 'os2web_audit.settings'; + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames(): array { + return [self::$configName]; + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'os2web_audit_admin_form'; + } + + /** + * {@inheritdoc} + */ + 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['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); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + parent::submitForm($form, $form_state); + + $this->config(self::$configName) + ->set('provider', $form_state->getValue('provider')) + ->set('fallback', $form_state->getValue('fallback')) + ->save(); + } + +} diff --git a/src/Plugin/AuditLogger/AuditLoggerInterface.php b/src/Plugin/AuditLogger/AuditLoggerInterface.php new file mode 100644 index 0000000..eacec53 --- /dev/null +++ b/src/Plugin/AuditLogger/AuditLoggerInterface.php @@ -0,0 +1,27 @@ +setConfiguration($configuration); + } + + /** + * {@inheritdoc} + */ + 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, + ]) . 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 [ + 'file' => '/tmp/audit.log', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { + + $form['file'] = [ + '#type' => 'textfield', + '#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. + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void { + if (!$form_state->getErrors()) { + $values = $form_state->getValues(); + $configuration = [ + 'file' => $values['file'], + ]; + $this->setConfiguration($configuration); + } + } + +} diff --git a/src/Plugin/AuditLogger/Loki.php b/src/Plugin/AuditLogger/Loki.php new file mode 100644 index 0000000..f7e94c2 --- /dev/null +++ b/src/Plugin/AuditLogger/Loki.php @@ -0,0 +1,142 @@ +setConfiguration($configuration); + } + + /** + * {@inheritdoc} + * + * @throws \JsonException + */ + 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); + } + + /** + * {@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', + ]; + } + + /** + * {@inheritdoc} + */ + 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'), + '#default_value' => $this->configuration['auth']['username'], + ], + 'password' => [ + '#type' => 'password', + '#title' => $this->t('Password'), + '#default_value' => $this->configuration['auth']['password'], + ], + ]; + + $form['curl_options'] = [ + '#type' => 'textfield', + '#title' => $this->t('cURL Options'), + '#default_value' => $this->configuration['curl_options'], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + 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.')); + } + + $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; + } + } + } + + /** + * {@inheritdoc} + */ + 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/src/Plugin/AuditLogger/Watchdog.php b/src/Plugin/AuditLogger/Watchdog.php new file mode 100644 index 0000000..e466ea1 --- /dev/null +++ b/src/Plugin/AuditLogger/Watchdog.php @@ -0,0 +1,34 @@ +info('%type: %line (%data)', [ + 'type' => $type, + 'line' => $line, + 'data' => $data, + ]); + } + +} diff --git a/src/Plugin/Derivative/LocalTask.php b/src/Plugin/Derivative/LocalTask.php new file mode 100644 index 0000000..3c8223e --- /dev/null +++ b/src/Plugin/Derivative/LocalTask.php @@ -0,0 +1,64 @@ +get('plugin.manager.os2web_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/src/Plugin/LoggerManager.php b/src/Plugin/LoggerManager.php new file mode 100644 index 0000000..751864a --- /dev/null +++ b/src/Plugin/LoggerManager.php @@ -0,0 +1,34 @@ +alterInfo('os2web_audit_logger_info'); + $this->setCacheBackend($cache_backend, 'os2web_audit_logger_plugins'); + } + +} diff --git a/src/Service/Logger.php b/src/Service/Logger.php new file mode 100644 index 0000000..c1a6078 --- /dev/null +++ b/src/Service/Logger.php @@ -0,0 +1,49 @@ +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 ?? []); + + $logger->log($type, $timestamp, $line, $metadata); + } + +} diff --git a/src/Service/LokiClient.php b/src/Service/LokiClient.php new file mode 100644 index 0000000..281ac66 --- /dev/null +++ b/src/Service/LokiClient.php @@ -0,0 +1,160 @@ + + */ + protected array $basicAuth = []; + + /** + * Custom options for CURL command. + * + * @var array + */ + protected array $customCurlOptions = []; + + /** + * Curl handler. + * + * @var \CurlHandle|null + */ + 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 = $apiConfig['curl_options'] ?? []; + + if (isset($apiConfig['auth']) && !empty($apiConfig['auth']['username']) && !empty($apiConfig['auth']['password'])) { + $this->basicAuth = $apiConfig['auth']; + } + } + + /** + * {@inheritdoc} + * + * @throws \JsonException + */ + public function send(string $label, int $epoch, string $line, array $metadata = []): void { + $packet = [ + 'streams' => [ + [ + 'stream' => [ + 'type' => $label, + ], + 'values' => [ + [(string) $epoch, $line], + ], + ], + ], + ]; + + if (!empty($metadata)) { + $packet['streams'][0]['stream'] += $metadata; + } + + $this->sendPacket($packet); + } + + /** + * 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. + * + * @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); + $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/src/Service/LokiClientInterface.php b/src/Service/LokiClientInterface.php new file mode 100644 index 0000000..0fcb1a7 --- /dev/null +++ b/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 b061683134af0d755c036dc8ec6b69988f1a7c45 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Thu, 25 Apr 2024 13:25:44 +0200 Subject: [PATCH 02/16] ITKDev: Added better logging options --- os2web_audit.info.yml | 2 +- os2web_audit.links.menu.yml | 4 +-- src/Service/Logger.php | 53 ++++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/os2web_audit.info.yml b/os2web_audit.info.yml index b0c7153..0c06595 100644 --- a/os2web_audit.info.yml +++ b/os2web_audit.info.yml @@ -1,4 +1,4 @@ name: OS2web Audit type: module -description: 'OS2web Audit Module (log all events to external service)' +description: 'OS2web Audit Module (log events in the system)' core_version_requirement: ^8 || ^9 || ^10 diff --git a/os2web_audit.links.menu.yml b/os2web_audit.links.menu.yml index 574ad72..269a099 100644 --- a/os2web_audit.links.menu.yml +++ b/os2web_audit.links.menu.yml @@ -1,5 +1,5 @@ os2web_audit.admin_settings: - title: 'OS2web Audit settings' + title: 'OS2 Audit settings' parent: system.admin_config_system - description: 'Settings for the OS2web Audit module' + description: 'Settings for the OS2 Audit module' route_name: os2web_audit.plugin_settings_local_tasks diff --git a/src/Service/Logger.php b/src/Service/Logger.php index c1a6078..1d28fb4 100644 --- a/src/Service/Logger.php +++ b/src/Service/Logger.php @@ -20,6 +20,48 @@ public function __construct( ) { } + /** + * Logs a message at info level. + * + * @param string $type + * The type of event to log (auth, lookup etc.) + * @param int $timestamp + * The timestamp for the log message. + * @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 message. Default is an empty array. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function info(string $type, int $timestamp, string $line, bool $logUser = false, array $metadata = []): void { + $this->log($type, $timestamp, $line, $logUser, $metadata + ['level' => 'info']); + } + + /** + * Logs a message at error level. + * + * @param string $type + * The type of event to log (auth, lookup etc.) + * @param int $timestamp + * The timestamp for the log message. + * @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 message. Default is an empty array. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function error(string $type, int $timestamp, string $line, bool $logUser = false, array $metadata = []): void { + $this->log($type, $timestamp, $line, $logUser, $metadata + ['level' => 'error']); + } + /** * Logs a message using a plugin-specific logger. * @@ -29,12 +71,15 @@ public function __construct( * The timestamp for the log message. * @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 message. Default is an empty array. * * @throws \Drupal\Component\Plugin\Exception\PluginException */ - public function log(string $type, int $timestamp, string $line, array $metadata = []): void { + private function log(string $type, int $timestamp, string $line, bool $logUser = false, array $metadata = []): void { $config = $this->configFactory->get(SettingsForm::$configName); $plugin_id = $config->get('provider'); @@ -43,6 +88,12 @@ public function log(string $type, int $timestamp, string $line, array $metadata $configuration = $this->configFactory->get(PluginSettingsForm::getConfigName())->get($plugin_id); $logger = $this->loggerManager->createInstance($plugin_id, $configuration ?? []); + if ($logUser) { + // Add user id to the log message metadata. + $user = \Drupal::currentUser(); + $metadata['userId'] = $user->id(); + } + $logger->log($type, $timestamp, $line, $metadata); } From d016055b678eb8f1dd29e4ba7022eabae6410aed Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Thu, 25 Apr 2024 13:43:40 +0200 Subject: [PATCH 03/16] ITKDev: Added code style checking --- .github/workflows/pr.yml | 137 ++++++++++++++++++ .gitignore | 2 + CHANGELOG.md | 13 ++ README.md | 4 +- composer.json | 64 ++++++++ package.json | 13 ++ phpcs.xml.dist | 27 ++++ phpstan.neon | 28 ++++ scripts/code-analysis | 36 +++++ src/Commands/AuditLogDrushCommands.php | 8 +- src/Controller/LocalTasksController.php | 2 +- src/Form/PluginSettingsForm.php | 1 - src/Form/PluginSettingsFormInterface.php | 20 +++ .../AuditLogger/AuditLoggerInterface.php | 2 +- src/Plugin/AuditLogger/File.php | 2 +- src/Plugin/AuditLogger/Watchdog.php | 12 +- src/Plugin/Derivative/LocalTask.php | 2 +- src/Service/Logger.php | 21 +-- src/Service/LokiClient.php | 4 +- src/Service/LokiClientInterface.php | 2 +- 20 files changed, 376 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/pr.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 composer.json create mode 100644 package.json create mode 100644 phpcs.xml.dist create mode 100644 phpstan.neon create mode 100755 scripts/code-analysis create mode 100644 src/Form/PluginSettingsFormInterface.php diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..42565ef --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,137 @@ +on: pull_request +name: Review +jobs: + changelog: + runs-on: ubuntu-latest + name: Changelog should be updated + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 2 + + - name: Git fetch + run: git fetch + + - name: Check that changelog has been updated. + run: git diff --exit-code origin/${{ github.base_ref }} -- CHANGELOG.md && exit 1 || exit 0 + + test-composer-files: + name: Validate composer + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: [ '8.1' ] + dependency-version: [ prefer-lowest, prefer-stable ] + steps: + - uses: actions/checkout@master + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: json + coverage: none + tools: composer:v2 + # https://github.com/shivammathur/setup-php#cache-composer-dependencies + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Validate composer files + run: | + composer validate --strict composer.json + # Check that dependencies resolve. + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + + php-coding-standards: + name: PHP coding standards + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: [ '8.1' ] + steps: + - uses: actions/checkout@master + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: json + coverage: none + tools: composer:v2 + # https://github.com/shivammathur/setup-php#cache-composer-dependencies + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install Dependencies + run: | + composer install --no-interaction --no-progress + - name: PHPCS + run: | + composer coding-standards-check/phpcs + + php-code-analysis: + name: PHP code analysis + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: [ '8.1' ] + steps: + - uses: actions/checkout@master + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: json + coverage: none + tools: composer:v2 + # https://github.com/shivammathur/setup-php#cache-composer-dependencies + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Code analysis + run: | + ./scripts/code-analysis + + markdownlint: + runs-on: ubuntu-latest + name: markdownlint + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache yarn packages + uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Yarn install + uses: actions/setup-node@v2 + with: + node-version: '20' + - run: yarn install + - name: markdownlint + run: yarn coding-standards-check/markdownlint diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8a7996 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..07231e3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +- First version of the module + +[Unreleased]: https://github.com/OS2web/os2web_audit/compare/develop...HEAD diff --git a/README.md b/README.md index c47ad4d..6bdf748 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ OS2Web Audit is a Drupal module that helps track changes and perform audit on OS2Web events. ## Requirements -- + - PHP 8.1 or higher - Drupal 8 or 9 - Composer for managing PHP dependencies ## Features -- + - Detailed audit log entries. - Support for all OS2Web entity types. - Easily extendable for custom use case. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8e1325b --- /dev/null +++ b/composer.json @@ -0,0 +1,64 @@ +{ + "name": "os2web/os2web_audit", + "type": "drupal-module", + "description": "Drupal OS2 module that provides audit logging for Danish Municipalities", + "minimum-stability": "dev", + "prefer-stable": true, + "license": "EUPL-1.2", + "repositories": { + "drupal": { + "type": "composer", + "url": "https://packages.drupal.org/8" + }, + "assets": { + "type": "composer", + "url": "https://asset-packagist.org" + } + }, + "require": { + "ext-curl": "*", + "php": "^8.1", + "drush/drush": "^11.6" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", + "drupal/coder": "^8.3", + "mglaman/phpstan-drupal": "^1.1", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpunit/phpunit": "^9.5" + }, + "extra" : { + "composer-exit-on-patch-failure": false, + "enable-patching" : true, + "patches": { + } + }, + "scripts": { + "code-analysis/phpstan": [ + "phpstan analyse" + ], + "code-analysis": [ + "@code-analysis/phpstan" + ], + "coding-standards-check/phpcs": [ + "phpcs --standard=phpcs.xml.dist" + ], + "coding-standards-check": [ + "@coding-standards-check/phpcs" + ], + "coding-standards-apply/phpcs": [ + "phpcbf --standard=phpcs.xml.dist" + ], + "coding-standards-apply": [ + "@coding-standards-apply/phpcs" + ] + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..52fcd34 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "license": "UNLICENSED", + "private": true, + "devDependencies": { + "markdownlint-cli": "^0.32.2" + }, + "scripts": { + "coding-standards-check/markdownlint": "yarn markdownlint --ignore LICENSE.md --ignore vendor --ignore node_modules '*.md' 'modules/os2forms_digital_post/**/*.md'", + "coding-standards-check": "yarn coding-standards-check/markdownlint", + "coding-standards-apply/markdownlint": "yarn markdownlint --ignore LICENSE.md --ignore vendor --ignore node_modules '*.md' 'modules/os2forms_digital_post/**/*.md' --fix", + "coding-standards-apply": "yarn coding-standards-apply/markdownlint" + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..07c8814 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,27 @@ + + + OS2Forms PHP Code Sniffer configuration + + . + vendor/ + node_modules/ + + modules/webform_embed/ + + modules/os2forms_webform_maps/modules/field_color/ + + + + + + + + + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..2715fef --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,28 @@ +parameters: + level: 6 + paths: + - ./ + excludePaths: + # @see https://github.com/mglaman/drupal-check/issues/261#issuecomment-1030141772/ + - vendor + - '*/node_modules/*' + ignoreErrors: + # This is how drupal works.... + - '#Unsafe usage of new static\(\).#' + - '#getEditableConfigNames\(\) return type has no value type specified in iterable type array#' + - '#buildForm\(\) has parameter \$form with no value type specified in iterable type array.#' + - '#buildForm\(\) return type has no value type specified in iterable type array.#' + - '#validateForm\(\) has parameter \$form with no value type specified in iterable type array.#' + - '#submitForm\(\) has parameter \$form with no value type specified in iterable type array.#' + - '#getDerivativeDefinitions\(\) has parameter \$base_plugin_definition with no value type specified in iterable type array.#' + - '#getDerivativeDefinitions\(\) return type has no value type specified in iterable type array.#' + - '#LoggerManager::__construct\(\) has parameter \$namespaces with no value type specified in iterable type Traversable.#' + - '#__construct\(\) has parameter \$configuration with no value type specified in iterable type array.#' + - '#getConfiguration\(\) return type has no value type specified in iterable type array.#' + - '#setConfiguration\(\) has parameter \$configuration with no value type specified in iterable type array.#' + - '#defaultConfiguration\(\) return type has no value type specified in iterable type array.#' + - '#buildConfigurationForm\(\) has parameter \$form with no value type specified in iterable type array.#' + - '#buildConfigurationForm\(\) return type has no value type specified in iterable type array.#' + - '#validateConfigurationForm\(\) has parameter \$form with no value type specified in iterable type array.#' + - '#submitConfigurationForm\(\) has parameter \$form with no value type specified in iterable type array.#' + - '#getForm\(\) invoked with 2 parameters, 1 required.#' diff --git a/scripts/code-analysis b/scripts/code-analysis new file mode 100755 index 0000000..f3544cd --- /dev/null +++ b/scripts/code-analysis @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +script_dir=$(pwd) +module_name=$(basename "$script_dir") +drupal_dir=vendor/drupal-module-code-analysis +# Relative to $drupal_dir +module_path=web/modules/contrib/$module_name + +cd "$script_dir" || exit + +drupal_composer() { + composer --working-dir="$drupal_dir" --no-interaction "$@" +} + +# Create new Drupal 9 project +if [ ! -f "$drupal_dir/composer.json" ]; then + composer --no-interaction create-project drupal/recommended-project:^9 "$drupal_dir" +fi +# Copy our code into the modules folder +mkdir -p "$drupal_dir/$module_path" +# https://stackoverflow.com/a/15373763 +rsync --archive --compress . --filter=':- .gitignore' --exclude "$drupal_dir" --exclude .git "$drupal_dir/$module_path" + +drupal_composer config minimum-stability dev + +# Allow ALL plugins +# https://getcomposer.org/doc/06-config.md#allow-plugins +drupal_composer config --no-plugins allow-plugins true + +drupal_composer require wikimedia/composer-merge-plugin +drupal_composer config extra.merge-plugin.include "$module_path/composer.json" +# https://www.drupal.org/project/drupal/issues/3220043#comment-14845434 +drupal_composer require --dev symfony/phpunit-bridge + +# Run PHPStan +(cd "$drupal_dir" && vendor/bin/phpstan --configuration="$module_path/phpstan.neon") diff --git a/src/Commands/AuditLogDrushCommands.php b/src/Commands/AuditLogDrushCommands.php index 68c9ca8..770f203 100644 --- a/src/Commands/AuditLogDrushCommands.php +++ b/src/Commands/AuditLogDrushCommands.php @@ -29,16 +29,18 @@ public function __construct( * Message to be logged. * * @command audit:log - * @usage audit:log 'This is a test message.' - * Logs 'This is a test message.' to the os2web_audit logger. + * @usage audit:log 'This is a test message' + * Logs 'This is a test message' to the os2web_audit logger. * * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Exception */ 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']); + $this->auditLogger->info('test', time(), $log_message, FALSE, ['from' => 'drush']); + $this->auditLogger->error('test', time(), $log_message, TRUE, ['from' => 'drush']); } } diff --git a/src/Controller/LocalTasksController.php b/src/Controller/LocalTasksController.php index c6bcd67..46a1dbf 100644 --- a/src/Controller/LocalTasksController.php +++ b/src/Controller/LocalTasksController.php @@ -44,7 +44,7 @@ public static function create(ContainerInterface $container): LocalTasksControll * @param string|null $type * The type of form to retrieve. Defaults to NULL. * - * @return array + * @return array * An array containing the form definition. */ public function dynamicTasks(string $type = NULL): array { diff --git a/src/Form/PluginSettingsForm.php b/src/Form/PluginSettingsForm.php index aef8709..ef0c918 100644 --- a/src/Form/PluginSettingsForm.php +++ b/src/Form/PluginSettingsForm.php @@ -11,7 +11,6 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\os2web_datalookup\Form\PluginSettingsFormInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** diff --git a/src/Form/PluginSettingsFormInterface.php b/src/Form/PluginSettingsFormInterface.php new file mode 100644 index 0000000..c8921d0 --- /dev/null +++ b/src/Form/PluginSettingsFormInterface.php @@ -0,0 +1,20 @@ + $metadata * Additional metadata associated with the log entry. Defaults to an empty * array. */ diff --git a/src/Plugin/AuditLogger/File.php b/src/Plugin/AuditLogger/File.php index 0028be6..4822eb1 100644 --- a/src/Plugin/AuditLogger/File.php +++ b/src/Plugin/AuditLogger/File.php @@ -81,7 +81,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta /** * {@inheritdoc} */ - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void { // @todo Implement validateConfigurationForm() method. } diff --git a/src/Plugin/AuditLogger/Watchdog.php b/src/Plugin/AuditLogger/Watchdog.php index e466ea1..5663a34 100644 --- a/src/Plugin/AuditLogger/Watchdog.php +++ b/src/Plugin/AuditLogger/Watchdog.php @@ -2,6 +2,7 @@ namespace Drupal\os2web_audit\Plugin\AuditLogger; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Plugin\PluginBase; /** @@ -15,6 +16,15 @@ */ class Watchdog extends PluginBase implements AuditLoggerInterface { + public function __construct( + array $configuration, + $plugin_id, + $plugin_definition, + private readonly LoggerChannelFactoryInterface $logger, + ) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + /** * {@inheritdoc} */ @@ -24,7 +34,7 @@ public function log(string $type, int $timestamp, string $line, array $metadata $data .= " $key=\"$val\""; }); - \Drupal::logger('os2web_audit')->info('%type: %line (%data)', [ + $this->logger->get('os2web_audit')->info('%type: %line (%data)', [ 'type' => $type, 'line' => $line, 'data' => $data, diff --git a/src/Plugin/Derivative/LocalTask.php b/src/Plugin/Derivative/LocalTask.php index 3c8223e..44f5877 100644 --- a/src/Plugin/Derivative/LocalTask.php +++ b/src/Plugin/Derivative/LocalTask.php @@ -31,7 +31,7 @@ public static function create(ContainerInterface $container, $base_plugin_id): L * * @throws \ReflectionException */ - public function getDerivativeDefinitions($base_plugin_definition) { + public function getDerivativeDefinitions($base_plugin_definition): array { $plugins = $this->loggerManager->getDefinitions(); ksort($plugins); diff --git a/src/Service/Logger.php b/src/Service/Logger.php index 1d28fb4..ff1e346 100644 --- a/src/Service/Logger.php +++ b/src/Service/Logger.php @@ -3,6 +3,7 @@ namespace Drupal\os2web_audit\Service; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Session\AccountProxyInterface; use Drupal\os2web_audit\Form\PluginSettingsForm; use Drupal\os2web_audit\Form\SettingsForm; use Drupal\os2web_audit\Plugin\LoggerManager; @@ -17,6 +18,7 @@ class Logger { public function __construct( private readonly LoggerManager $loggerManager, private readonly ConfigFactoryInterface $configFactory, + private readonly AccountProxyInterface $currentUser, ) { } @@ -32,12 +34,12 @@ public function __construct( * @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 + * @param array $metadata * Additional metadata for the log message. Default is an empty array. * * @throws \Drupal\Component\Plugin\Exception\PluginException */ - public function info(string $type, int $timestamp, string $line, bool $logUser = false, array $metadata = []): void { + public function info(string $type, int $timestamp, string $line, bool $logUser = FALSE, array $metadata = []): void { $this->log($type, $timestamp, $line, $logUser, $metadata + ['level' => 'info']); } @@ -53,12 +55,12 @@ public function info(string $type, int $timestamp, string $line, bool $logUser = * @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 + * @param array $metadata * Additional metadata for the log message. Default is an empty array. * * @throws \Drupal\Component\Plugin\Exception\PluginException */ - public function error(string $type, int $timestamp, string $line, bool $logUser = false, array $metadata = []): void { + public function error(string $type, int $timestamp, string $line, bool $logUser = FALSE, array $metadata = []): void { $this->log($type, $timestamp, $line, $logUser, $metadata + ['level' => 'error']); } @@ -74,24 +76,23 @@ public function error(string $type, int $timestamp, string $line, bool $logUser * @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 + * @param array $metadata * Additional metadata for the log message. Default is an empty array. * * @throws \Drupal\Component\Plugin\Exception\PluginException */ - private function log(string $type, int $timestamp, string $line, bool $logUser = false, array $metadata = []): void { + private function log(string $type, int $timestamp, string $line, bool $logUser = FALSE, array $metadata = []): void { $config = $this->configFactory->get(SettingsForm::$configName); $plugin_id = $config->get('provider'); - // @todo: default logger (file) - // @todo: Fallback logger on error. + // @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 ?? []); if ($logUser) { // Add user id to the log message metadata. - $user = \Drupal::currentUser(); - $metadata['userId'] = $user->id(); + $metadata['userId'] = $this->currentUser->id(); } $logger->log($type, $timestamp, $line, $metadata); diff --git a/src/Service/LokiClient.php b/src/Service/LokiClient.php index 281ac66..8c9905d 100644 --- a/src/Service/LokiClient.php +++ b/src/Service/LokiClient.php @@ -40,7 +40,7 @@ class LokiClient implements LokiClientInterface { /** * Default constructor. * - * @param array $apiConfig + * @param array> $apiConfig * Configuration for the loki connection. */ public function __construct( @@ -100,7 +100,7 @@ private function getEntrypoint(string $entrypoint): string { /** * Send a packet to the Loki ingestion endpoint. * - * @param array $packet + * @param array $packet * The packet to send. * * @throws \JsonException diff --git a/src/Service/LokiClientInterface.php b/src/Service/LokiClientInterface.php index 0fcb1a7..44ef364 100644 --- a/src/Service/LokiClientInterface.php +++ b/src/Service/LokiClientInterface.php @@ -30,7 +30,7 @@ interface LokiClientInterface { * Unix epoch in nanoseconds. * @param string $line * The log line to send. - * @param array $metadata + * @param array $metadata * Extra labels/metadata to filter on. * * @see https://grafana.com/docs/loki/latest/reference/api/#ingest-logs From 5adceb0a194cb2930225c2a07660d5942c7c131a Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 26 Apr 2024 12:26:26 +0200 Subject: [PATCH 04/16] ITKDev: Added exception hanling --- .github/PULL_REQUEST_TEMPLATE.md | 23 +++++++++ os2web_audit.info.yml | 1 + os2web_audit.links.task.yml | 2 +- os2web_audit.module | 6 --- os2web_audit.routing.yml | 2 +- os2web_audit.services.yml | 2 +- src/Commands/AuditLogDrushCommands.php | 4 +- src/Exception/AuditException.php | 37 +++++++++++++++ src/Exception/ConnectionException.php | 11 +++++ src/Form/SettingsForm.php | 9 ---- .../AuditLogger/AuditLoggerInterface.php | 5 ++ src/Plugin/AuditLogger/Loki.php | 5 +- src/Plugin/Derivative/LocalTask.php | 8 ++-- src/Service/Logger.php | 47 +++++++++++-------- src/Service/LokiClient.php | 46 +++++++++++++----- 15 files changed, 152 insertions(+), 56 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 os2web_audit.module create mode 100644 src/Exception/AuditException.php create mode 100644 src/Exception/ConnectionException.php diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a4b263e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +#### Link to ticket + +Please add a link to the ticket being addressed by this change. + +#### Description + +Please include a short description of the suggested change and the reasoning behind the approach you have chosen. + +#### Screenshot of the result + +If your change affects the user interface you should include a screenshot of the result with the pull request. + +#### Checklist + +- [ ] My code passes our static analysis suite. +- [ ] My code passes our continuous integration process. + +If your code does not pass all the requirements on the checklist you have to add a comment explaining why this change +should be exempt from the list. + +#### Additional comments or questions + +If you have any further comments or questions for the reviewer please add them here. diff --git a/os2web_audit.info.yml b/os2web_audit.info.yml index 0c06595..5a23a63 100644 --- a/os2web_audit.info.yml +++ b/os2web_audit.info.yml @@ -2,3 +2,4 @@ name: OS2web Audit type: module description: 'OS2web Audit Module (log events in the system)' core_version_requirement: ^8 || ^9 || ^10 +configure: os2web_audit.plugin_settings_local_tasks diff --git a/os2web_audit.links.task.yml b/os2web_audit.links.task.yml index b2303c7..788a7ae 100644 --- a/os2web_audit.links.task.yml +++ b/os2web_audit.links.task.yml @@ -1,5 +1,5 @@ os2web_audit.plugin_settings_tasks: - title: 'Dynamic tasks' + title: 'OS2 Audit settings' route_name: os2web_audit.plugin_settings_local_tasks base_route: os2web_audit.plugin_settings_local_tasks deriver: Drupal\os2web_audit\Plugin\Derivative\LocalTask diff --git a/os2web_audit.module b/os2web_audit.module deleted file mode 100644 index 29c53cf..0000000 --- a/os2web_audit.module +++ /dev/null @@ -1,6 +0,0 @@ -auditLogger->info('test', time(), $log_message, FALSE, ['from' => 'drush']); - $this->auditLogger->error('test', time(), $log_message, TRUE, ['from' => 'drush']); + $this->auditLogger->info('test', $log_message, FALSE, ['from' => 'drush']); + $this->auditLogger->error('test', $log_message, TRUE, ['from' => 'drush']); } } diff --git a/src/Exception/AuditException.php b/src/Exception/AuditException.php new file mode 100644 index 0000000..1ebd9ee --- /dev/null +++ b/src/Exception/AuditException.php @@ -0,0 +1,37 @@ +pluginName = $pluginName; + } + } + + /** + * Name of the plugin that started the exception. + * + * @return string + * Name of the plugin if given else "Unknown plugin". + */ + public function getPluginName(): string { + return $this->pluginName; + } + +} diff --git a/src/Exception/ConnectionException.php b/src/Exception/ConnectionException.php new file mode 100644 index 0000000..4d89181 --- /dev/null +++ b/src/Exception/ConnectionException.php @@ -0,0 +1,11 @@ + $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); } @@ -97,7 +89,6 @@ public function submitForm(array &$form, FormStateInterface $form_state): void { $this->config(self::$configName) ->set('provider', $form_state->getValue('provider')) - ->set('fallback', $form_state->getValue('fallback')) ->save(); } diff --git a/src/Plugin/AuditLogger/AuditLoggerInterface.php b/src/Plugin/AuditLogger/AuditLoggerInterface.php index 8ffdb25..2a2a8b9 100644 --- a/src/Plugin/AuditLogger/AuditLoggerInterface.php +++ b/src/Plugin/AuditLogger/AuditLoggerInterface.php @@ -21,6 +21,11 @@ interface AuditLoggerInterface extends PluginInspectionInterface { * @param array $metadata * Additional metadata associated with the log entry. Defaults to an empty * array. + * + * @throws \Drupal\os2web_audit\Exception\ConnectionException + * If unable to connect to the Loki endpoint. + * @throws \Drupal\os2web_audit\Exception\AuditException + * Errors in logging the packet. */ public function log(string $type, int $timestamp, string $line, array $metadata = []): void; diff --git a/src/Plugin/AuditLogger/Loki.php b/src/Plugin/AuditLogger/Loki.php index f7e94c2..362b969 100644 --- a/src/Plugin/AuditLogger/Loki.php +++ b/src/Plugin/AuditLogger/Loki.php @@ -30,7 +30,10 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition /** * {@inheritdoc} * - * @throws \JsonException + * @throws \Drupal\os2web_audit\Exception\ConnectionException + * If unable to connect to the Loki endpoint. + * @throws \Drupal\os2web_audit\Exception\AuditException + * Errors in logging the packet. */ public function log(string $type, int $timestamp, string $line, array $metadata = []): void { $client = new LokiClient([ diff --git a/src/Plugin/Derivative/LocalTask.php b/src/Plugin/Derivative/LocalTask.php index 44f5877..c9858db 100644 --- a/src/Plugin/Derivative/LocalTask.php +++ b/src/Plugin/Derivative/LocalTask.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides dynamic tabs based on node types. + * Provides dynamic tabs based on plugins available. */ class LocalTask extends DeriverBase implements ContainerDeriverInterface { @@ -35,12 +35,12 @@ public function getDerivativeDefinitions($base_plugin_definition): array { $plugins = $this->loggerManager->getDefinitions(); ksort($plugins); - // Sadly it seems that it is not possible to just invalidate the + // 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 + // of clearing 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. + // Only the plugins that provide configuration options. $reflector = new \ReflectionClass($plugin['class']); if ($reflector->implementsInterface('Drupal\Component\Plugin\ConfigurableInterface')) { /** @var \Drupal\Core\StringTranslation\TranslatableMarkup $title */ diff --git a/src/Service/Logger.php b/src/Service/Logger.php index ff1e346..fe0b599 100644 --- a/src/Service/Logger.php +++ b/src/Service/Logger.php @@ -2,8 +2,12 @@ namespace Drupal\os2web_audit\Service; +use Drupal\Component\Plugin\Exception\PluginException; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Session\AccountProxyInterface; +use Drupal\os2web_audit\Exception\AuditException; +use Drupal\os2web_audit\Exception\ConnectionException; use Drupal\os2web_audit\Form\PluginSettingsForm; use Drupal\os2web_audit\Form\SettingsForm; use Drupal\os2web_audit\Plugin\LoggerManager; @@ -19,6 +23,7 @@ public function __construct( private readonly LoggerManager $loggerManager, private readonly ConfigFactoryInterface $configFactory, private readonly AccountProxyInterface $currentUser, + private readonly LoggerChannelFactoryInterface $watchdog, ) { } @@ -27,8 +32,6 @@ public function __construct( * * @param string $type * The type of event to log (auth, lookup etc.) - * @param int $timestamp - * The timestamp for the log message. * @param string $line * The log message. * @param bool $logUser @@ -36,11 +39,9 @@ public function __construct( * lookup information in external services). Default: false. * @param array $metadata * Additional metadata for the log message. Default is an empty array. - * - * @throws \Drupal\Component\Plugin\Exception\PluginException */ - public function info(string $type, int $timestamp, string $line, bool $logUser = FALSE, array $metadata = []): void { - $this->log($type, $timestamp, $line, $logUser, $metadata + ['level' => 'info']); + public function info(string $type, string $line, bool $logUser = TRUE, array $metadata = []): void { + $this->log($type, time(), $line, $logUser, $metadata + ['level' => 'info']); } /** @@ -48,8 +49,6 @@ public function info(string $type, int $timestamp, string $line, bool $logUser = * * @param string $type * The type of event to log (auth, lookup etc.) - * @param int $timestamp - * The timestamp for the log message. * @param string $line * The log message. * @param bool $logUser @@ -57,11 +56,9 @@ public function info(string $type, int $timestamp, string $line, bool $logUser = * lookup information in external services). Default: false. * @param array $metadata * Additional metadata for the log message. Default is an empty array. - * - * @throws \Drupal\Component\Plugin\Exception\PluginException */ - public function error(string $type, int $timestamp, string $line, bool $logUser = FALSE, array $metadata = []): void { - $this->log($type, $timestamp, $line, $logUser, $metadata + ['level' => 'error']); + public function error(string $type, string $line, bool $logUser = TRUE, array $metadata = []): void { + $this->log($type, time(), $line, $logUser, $metadata + ['level' => 'error']); } /** @@ -78,24 +75,36 @@ public function error(string $type, int $timestamp, string $line, bool $logUser * lookup information in external services). Default: false. * @param array $metadata * Additional metadata for the log message. Default is an empty array. - * - * @throws \Drupal\Component\Plugin\Exception\PluginException */ private function log(string $type, int $timestamp, string $line, bool $logUser = FALSE, array $metadata = []): void { $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 ?? []); if ($logUser) { // Add user id to the log message metadata. $metadata['userId'] = $this->currentUser->id(); } - $logger->log($type, $timestamp, $line, $metadata); + try { + /** @var \Drupal\os2web_audit\Plugin\AuditLogger\AuditLoggerInterface $logger */ + $logger = $this->loggerManager->createInstance($plugin_id, $configuration ?? []); + $logger->log($type, $timestamp, $line, $metadata); + } + catch (PluginException $e) { + $this->watchdog->get('os2web_audit')->error($e->getMessage()); + } + catch (AuditException | ConnectionException $e) { + // Change metadata into string. + $data = implode(', ', array_map(function ($key, $value) { + return $key . " => " . $value; + }, array_keys($metadata), $metadata)); + + // Fallback to send log message info watchdog. + $msg = sprintf("Plugin: %s, Type: %s, Msg: %s, Metadata: %s", $e->getPluginName(), $type, $line, $data); + $this->watchdog->get('os2web_audit')->info($msg); + $this->watchdog->get('os2web_audit_error')->error($e->getMessage()); + } } } diff --git a/src/Service/LokiClient.php b/src/Service/LokiClient.php index 8c9905d..1b35df5 100644 --- a/src/Service/LokiClient.php +++ b/src/Service/LokiClient.php @@ -2,6 +2,9 @@ namespace Drupal\os2web_audit\Service; +use Drupal\os2web_audit\Exception\AuditException; +use Drupal\os2web_audit\Exception\ConnectionException; + /** * Class LokiClient. * @@ -57,7 +60,10 @@ public function __construct( /** * {@inheritdoc} * - * @throws \JsonException + * @throws \Drupal\os2web_audit\Exception\ConnectionException + * If unable to connect to the Loki endpoint. + * @throws \Drupal\os2web_audit\Exception\AuditException + * Errors in logging the packet. */ public function send(string $label, int $epoch, string $line, array $metadata = []): void { $packet = [ @@ -103,20 +109,32 @@ private function getEntrypoint(string $entrypoint): string { * @param array $packet * The packet to send. * - * @throws \JsonException - * If unable to encode the packet to JSON. - * @throws \LogicException + * @throws \Drupal\os2web_audit\Exception\ConnectionException * If unable to connect to the Loki endpoint. + * @throws \Drupal\os2web_audit\Exception\AuditException + * Errors in logging the packet. */ 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); + try { + $payload = json_encode($packet, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } + catch (\JsonException $e) { + throw new AuditException( + message: 'Payload could not be encoded.', + previous: $e, + pluginName: 'Loki', + ); + } if (NULL === $this->connection) { + $url = sprintf('%s/loki/api/v1/push', $this->entrypoint); $this->connection = curl_init($url); if (FALSE === $this->connection) { - throw new \LogicException('Unable to connect to ' . $url); + throw new ConnectionException( + message: 'Unable to connect to ' . $url, + pluginName: 'Loki', + ); } } @@ -145,14 +163,18 @@ private function sendPacket(array $packet): void { $result = curl_exec($this->connection); if (FALSE === $result) { - throw new \RuntimeException('Error sending packet to Loki'); + throw new ConnectionException( + message: 'Error sending packet to Loki', + pluginName: 'Loki', + ); } if (curl_errno($this->connection)) { - echo 'Curl error: ' . curl_error($this->connection); - } - else { - echo 'Curl result: ' . $result; + throw new AuditException( + message: curl_error($this->connection), + code: curl_errno($this->connection), + pluginName: 'Loki', + ); } } } From fcbb5a9311a5dc75bd0d1ee0d8622b5bb52e70b3 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Tue, 30 Apr 2024 15:43:37 +0200 Subject: [PATCH 05/16] ITKDev: Added identity to loki log messages --- src/Plugin/AuditLogger/Loki.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Plugin/AuditLogger/Loki.php b/src/Plugin/AuditLogger/Loki.php index 362b969..ea489f5 100644 --- a/src/Plugin/AuditLogger/Loki.php +++ b/src/Plugin/AuditLogger/Loki.php @@ -40,6 +40,13 @@ public function log(string $type, int $timestamp, string $line, array $metadata 'entrypoint' => $this->configuration['entrypoint'], 'auth' => $this->configuration['auth'], ]); + + // Add 'identity' to metadata to be able to filter out all messages from + // this site in loki. + if (!empty($this->configuration['identity'])) { + $metadata['identity'] = $this->configuration['identity']; + } + // Convert timestamp to nanoseconds. $client->send($type, $timestamp * 1000000000, $line, $metadata); } @@ -93,6 +100,13 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ], ]; + $form['identity'] = [ + '#type' => 'textfield', + '#title' => t('Identity'), + '#default_value' => $this->configuration['identity'], + '#description' => t('A string that will be attached to every log sendt to loki'), + ]; + $form['curl_options'] = [ '#type' => 'textfield', '#title' => $this->t('cURL Options'), @@ -136,6 +150,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s 'username' => $values['auth']['username'], 'password' => $values['auth']['password'], ], + 'identity' => $values['identity'], 'curl_options' => $values['curl_options'], ]; $this->setConfiguration($configuration); From f10132bf47f0d1b8f25d8a053049f5e03f7834c0 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Tue, 30 Apr 2024 16:14:41 +0200 Subject: [PATCH 06/16] ITKDev: Tried to update readme with usefull information --- README.md | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 6bdf748..bec9a9b 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,45 @@ -# OS2Web Audit Module +# OS2Web Audit -OS2Web Audit is a Drupal module that helps track changes and perform audit on -OS2Web events. - -## Requirements - -- PHP 8.1 or higher -- Drupal 8 or 9 -- Composer for managing PHP dependencies +This audit module can be used to track changes and perform audit logging on +drupal sites. ## Features -- Detailed audit log entries. -- Support for all OS2Web entity types. -- Easily extendable for custom use case. +This module includes three plugins that facilitate logging information to Loki, +files, or to the database through Drupal's watchdog logger. + +These logging providers are designed using Drupal's plugin APIs. Consequently, +it opens up possibilities for creating new AuditLogger plugins within other +modules, thus enhancing the functionality of this audit logging. ## Installation -### Composer +Enable the module and go to the modules setting page at +`/admin/config/os2web_audit/settings/`. ### Drush -### Admin UI - -Alternatively, you can enable this module via Drupal admin UI by visiting the -extent page (`/admin/modules`). - -## Configuration +The module provides a Drush command named audit:log. This command enables you +to log a test message to the configured logger. The audit:log command accepts a +string that represents the message to be logged. -Navigate to `/admin/config/audit` to configure the module. +The message provided, will be logged twice, once as an informational message +and once as an error message. -Some additional (brief) information on Usage, Extending/Overriding, and -Maintainers could go here. +#### Usage +```shell +drush audit:log 'This is a test message' +``` ## Usage -Describe how to use this module after installation. +The module exposes n simple `Logger` service which can log an `info` and`error` +messages. -## Extending and Overriding +Inject the logger service named `os2web_audit.logger` and send messages into the +logger as shown below: -Describe how to extend or override this module's functionality. +```php +$msg = sprintf('Fetch personal data from service with parameter: %s', $param); +$this->auditLogger->info('Lookup', $msg); +``` From 52aeaac45103bd82e0c386c1fe30e5e46d05b297 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 1 May 2024 11:01:35 +0200 Subject: [PATCH 07/16] ITKDev: Fixed code style --- README.md | 1 - src/Plugin/AuditLogger/Loki.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bec9a9b..63a8327 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ string that represents the message to be logged. The message provided, will be logged twice, once as an informational message and once as an error message. -#### Usage ```shell drush audit:log 'This is a test message' ``` diff --git a/src/Plugin/AuditLogger/Loki.php b/src/Plugin/AuditLogger/Loki.php index ea489f5..aa57d0d 100644 --- a/src/Plugin/AuditLogger/Loki.php +++ b/src/Plugin/AuditLogger/Loki.php @@ -102,9 +102,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $form['identity'] = [ '#type' => 'textfield', - '#title' => t('Identity'), + '#title' => $this->t('Identity'), '#default_value' => $this->configuration['identity'], - '#description' => t('A string that will be attached to every log sendt to loki'), + '#description' => $this->t('A string that will be attached to every log sendt to loki'), ]; $form['curl_options'] = [ From 0c0866563e40c39a64b039dec9c7dfc71bcd77e2 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:38:45 +0200 Subject: [PATCH 08/16] ITKDev: Minor user id fix --- os2web_audit.info.yml | 4 ++-- src/Service/Logger.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/os2web_audit.info.yml b/os2web_audit.info.yml index 5a23a63..fddb829 100644 --- a/os2web_audit.info.yml +++ b/os2web_audit.info.yml @@ -1,5 +1,5 @@ -name: OS2web Audit -type: module +name: "OS2web Audit" description: 'OS2web Audit Module (log events in the system)' +type: module core_version_requirement: ^8 || ^9 || ^10 configure: os2web_audit.plugin_settings_local_tasks diff --git a/src/Service/Logger.php b/src/Service/Logger.php index fe0b599..8101a05 100644 --- a/src/Service/Logger.php +++ b/src/Service/Logger.php @@ -83,7 +83,7 @@ private function log(string $type, int $timestamp, string $line, bool $logUser = if ($logUser) { // Add user id to the log message metadata. - $metadata['userId'] = $this->currentUser->id(); + $metadata['userId'] = $this->currentUser->getEmail(); } try { From 575cb091afdfa7800a446d5813332bad01f04eb1 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:44:56 +0200 Subject: [PATCH 09/16] ITKDev: Update README.md Co-authored-by: Jeppe Kuhlmann Andersen <78410897+jekuaitk@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63a8327..27c04ce 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ drush audit:log 'This is a test message' ## Usage -The module exposes n simple `Logger` service which can log an `info` and`error` +The module exposes a simple `Logger` service which can log an `info` and `error` messages. Inject the logger service named `os2web_audit.logger` and send messages into the From 12d73493d1ca2703acf4eb9188dda8d20a901087 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:45:07 +0200 Subject: [PATCH 10/16] ITKDev: Update os2web_audit.links.menu.yml Co-authored-by: Jeppe Kuhlmann Andersen <78410897+jekuaitk@users.noreply.github.com> --- os2web_audit.links.menu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os2web_audit.links.menu.yml b/os2web_audit.links.menu.yml index 269a099..c6d73da 100644 --- a/os2web_audit.links.menu.yml +++ b/os2web_audit.links.menu.yml @@ -1,5 +1,5 @@ os2web_audit.admin_settings: - title: 'OS2 Audit settings' + title: 'OS2web Audit settings' parent: system.admin_config_system description: 'Settings for the OS2 Audit module' route_name: os2web_audit.plugin_settings_local_tasks From 9228695ac198c26fe093fabf1d1bcfe7c80e2e78 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:45:17 +0200 Subject: [PATCH 11/16] ITKDev: Update os2web_audit.links.task.yml Co-authored-by: Jeppe Kuhlmann Andersen <78410897+jekuaitk@users.noreply.github.com> --- os2web_audit.links.task.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os2web_audit.links.task.yml b/os2web_audit.links.task.yml index 788a7ae..31e7570 100644 --- a/os2web_audit.links.task.yml +++ b/os2web_audit.links.task.yml @@ -1,5 +1,5 @@ os2web_audit.plugin_settings_tasks: - title: 'OS2 Audit settings' + title: 'OS2web Audit settings' route_name: os2web_audit.plugin_settings_local_tasks base_route: os2web_audit.plugin_settings_local_tasks deriver: Drupal\os2web_audit\Plugin\Derivative\LocalTask From 6efc85e659c794063d50dab1afc6ae843dcf0f69 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:45:26 +0200 Subject: [PATCH 12/16] ITKDev: Update os2web_audit.routing.yml Co-authored-by: Jeppe Kuhlmann Andersen <78410897+jekuaitk@users.noreply.github.com> --- os2web_audit.routing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os2web_audit.routing.yml b/os2web_audit.routing.yml index bda09b6..cd46d48 100644 --- a/os2web_audit.routing.yml +++ b/os2web_audit.routing.yml @@ -2,7 +2,7 @@ os2web_audit.plugin_settings_local_tasks: path: '/admin/config/os2web_audit/settings/{type}' defaults: _controller: '\Drupal\os2web_audit\Controller\LocalTasksController::dynamicTasks' - _title: 'OS2 Audit settings' + _title: 'OS2web Audit settings' type: '' requirements: _permission: 'administer site' From 7d9ebb0c611133b22375f839119ca881684f1cb3 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:45:45 +0200 Subject: [PATCH 13/16] ITKDev: Update phpcs.xml.dist Co-authored-by: Jeppe Kuhlmann Andersen <78410897+jekuaitk@users.noreply.github.com> --- phpcs.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 07c8814..c9fec00 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -1,6 +1,6 @@ - OS2Forms PHP Code Sniffer configuration + OS2web Audit PHP Code Sniffer configuration . vendor/ From caa34a69112910564f9cef923f296dc829dcc727 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:46:00 +0200 Subject: [PATCH 14/16] ITKDev: Update phpcs.xml.dist Co-authored-by: Jeppe Kuhlmann Andersen <78410897+jekuaitk@users.noreply.github.com> --- phpcs.xml.dist | 4 ---- 1 file changed, 4 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index c9fec00..e6cd9bb 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -5,10 +5,6 @@ . vendor/ node_modules/ - - modules/webform_embed/ - - modules/os2forms_webform_maps/modules/field_color/ From 29dcb76cd354900c820f9b58a9a54f6685ee62e6 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:47:51 +0200 Subject: [PATCH 15/16] ITKDev: Update src/Form/PluginSettingsForm.php Co-authored-by: Jeppe Kuhlmann Andersen <78410897+jekuaitk@users.noreply.github.com> --- src/Form/PluginSettingsForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Form/PluginSettingsForm.php b/src/Form/PluginSettingsForm.php index ef0c918..071b2e5 100644 --- a/src/Form/PluginSettingsForm.php +++ b/src/Form/PluginSettingsForm.php @@ -4,7 +4,7 @@ /** * @file - * Abstract class for PluginSettingsForm implementation. + * Class for PluginSettingsForm implementation. */ use Drupal\Component\Plugin\PluginManagerInterface; From 029b8b343d9d74bfa5fbb07887de0e9ab9c72388 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Fri, 3 May 2024 13:54:16 +0200 Subject: [PATCH 16/16] ITKDev: Minor code review changes --- README.md | 5 +++++ src/Plugin/AuditLogger/AuditLoggerInterface.php | 4 ++-- src/Plugin/AuditLogger/File.php | 4 ++-- src/Plugin/AuditLogger/Loki.php | 6 +++--- src/Plugin/AuditLogger/Watchdog.php | 4 ++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 63a8327..9e6dd58 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,11 @@ modules, thus enhancing the functionality of this audit logging. Enable the module and go to the modules setting page at `/admin/config/os2web_audit/settings/`. +```shell +composer require os2web/os2web_audit +drush pm:enable os2web_audit +``` + ### Drush The module provides a Drush command named audit:log. This command enables you diff --git a/src/Plugin/AuditLogger/AuditLoggerInterface.php b/src/Plugin/AuditLogger/AuditLoggerInterface.php index 2a2a8b9..d76eaeb 100644 --- a/src/Plugin/AuditLogger/AuditLoggerInterface.php +++ b/src/Plugin/AuditLogger/AuditLoggerInterface.php @@ -16,7 +16,7 @@ interface AuditLoggerInterface extends PluginInspectionInterface { * The type of event to log (auth, lookup etc.) * @param int $timestamp * The timestamp of the log entry. - * @param string $line + * @param string $message * The log message. * @param array $metadata * Additional metadata associated with the log entry. Defaults to an empty @@ -27,6 +27,6 @@ interface AuditLoggerInterface extends PluginInspectionInterface { * @throws \Drupal\os2web_audit\Exception\AuditException * Errors in logging the packet. */ - public function log(string $type, int $timestamp, string $line, array $metadata = []): void; + public function log(string $type, int $timestamp, string $message, array $metadata = []): void; } diff --git a/src/Plugin/AuditLogger/File.php b/src/Plugin/AuditLogger/File.php index 4822eb1..e0eb7ae 100644 --- a/src/Plugin/AuditLogger/File.php +++ b/src/Plugin/AuditLogger/File.php @@ -26,7 +26,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition /** * {@inheritdoc} */ - public function log(string $type, int $timestamp, string $line, array $metadata = []): void { + public function log(string $type, int $timestamp, string $message, 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( @@ -34,7 +34,7 @@ public function log(string $type, int $timestamp, string $line, array $metadata json_encode([ 'type' => $type, 'epoc' => $timestamp, - 'line' => $line, + 'line' => $message, 'metadata' => $metadata, ]) . PHP_EOL, FILE_APPEND | LOCK_EX); diff --git a/src/Plugin/AuditLogger/Loki.php b/src/Plugin/AuditLogger/Loki.php index aa57d0d..cfd5627 100644 --- a/src/Plugin/AuditLogger/Loki.php +++ b/src/Plugin/AuditLogger/Loki.php @@ -35,7 +35,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * @throws \Drupal\os2web_audit\Exception\AuditException * Errors in logging the packet. */ - public function log(string $type, int $timestamp, string $line, array $metadata = []): void { + public function log(string $type, int $timestamp, string $message, array $metadata = []): void { $client = new LokiClient([ 'entrypoint' => $this->configuration['entrypoint'], 'auth' => $this->configuration['auth'], @@ -48,7 +48,7 @@ public function log(string $type, int $timestamp, string $line, array $metadata } // Convert timestamp to nanoseconds. - $client->send($type, $timestamp * 1000000000, $line, $metadata); + $client->send($type, $timestamp * 1000000000, $message, $metadata); } /** @@ -131,7 +131,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form foreach ($curlOptions as $option) { [$key] = explode(' =>', $option); $key = trim($key); - if (!defined($key)) { + if (!(str_starts_with($key, 'CURLOPT') && defined($key))) { $form_state->setErrorByName('curl_options', $this->t('%option is not a valid cURL option.', ['%option' => $key])); break; } diff --git a/src/Plugin/AuditLogger/Watchdog.php b/src/Plugin/AuditLogger/Watchdog.php index 5663a34..b368bb9 100644 --- a/src/Plugin/AuditLogger/Watchdog.php +++ b/src/Plugin/AuditLogger/Watchdog.php @@ -28,7 +28,7 @@ public function __construct( /** * {@inheritdoc} */ - public function log(string $type, int $timestamp, string $line, array $metadata = []): void { + public function log(string $type, int $timestamp, string $message, array $metadata = []): void { $data = ''; array_walk($metadata, function ($val, $key) use (&$data) { $data .= " $key=\"$val\""; @@ -36,7 +36,7 @@ public function log(string $type, int $timestamp, string $line, array $metadata $this->logger->get('os2web_audit')->info('%type: %line (%data)', [ 'type' => $type, - 'line' => $line, + 'line' => $message, 'data' => $data, ]); }