From ae977101b925f35b80bbaccf03923100b70b9ab2 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Fri, 4 Oct 2024 14:55:59 +0300 Subject: [PATCH 01/88] DIgital signature first version --- .../src/Element/AttachmentElement.php | 46 ++- .../WebformElement/AttachmentElement.php | 6 + .../os2forms_digital_signature.info.yml | 9 + .../os2forms_digital_signature.module | 28 ++ .../os2forms_digital_signature.routing.yml | 18 ++ .../os2forms_digital_signature.services.yml | 4 + .../Controller/DigitalSignatureController.php | 139 +++++++++ .../DigitalSignatureWebformHandler.php | 293 ++++++++++++++++++ .../src/Service/SigningService.php | 156 ++++++++++ .../src/Service/SigningUtil.php | 93 ++++++ 10 files changed, 780 insertions(+), 12 deletions(-) create mode 100644 modules/os2forms_digital_signature/os2forms_digital_signature.info.yml create mode 100644 modules/os2forms_digital_signature/os2forms_digital_signature.module create mode 100644 modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml create mode 100644 modules/os2forms_digital_signature/os2forms_digital_signature.services.yml create mode 100644 modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php create mode 100644 modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php create mode 100644 modules/os2forms_digital_signature/src/Service/SigningService.php create mode 100644 modules/os2forms_digital_signature/src/Service/SigningUtil.php diff --git a/modules/os2forms_attachment/src/Element/AttachmentElement.php b/modules/os2forms_attachment/src/Element/AttachmentElement.php index 05d37e4c..43326d20 100644 --- a/modules/os2forms_attachment/src/Element/AttachmentElement.php +++ b/modules/os2forms_attachment/src/Element/AttachmentElement.php @@ -2,6 +2,7 @@ namespace Drupal\os2forms_attachment\Element; +use Drupal\Core\File\FileSystemInterface; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform_attachment\Element\WebformAttachmentBase; @@ -20,6 +21,7 @@ public function getInfo() { return parent::getInfo() + [ '#view_mode' => 'html', '#export_type' => 'pdf', + '#digital_signature' => FALSE, '#template' => '', ]; } @@ -28,6 +30,8 @@ public function getInfo() { * {@inheritdoc} */ public static function getFileContent(array $element, WebformSubmissionInterface $webform_submission) { + $submissionUuid = $webform_submission->uuid(); + // Override webform settings. static::overrideWebformSettings($element, $webform_submission); @@ -51,18 +55,36 @@ public static function getFileContent(array $element, WebformSubmissionInterface \Drupal::request()->request->set('_webform_submissions_view_mode', $view_mode); if ($element['#export_type'] === 'pdf') { - // Get scheme. - $scheme = 'temporary'; - - // Get filename. - $file_name = 'webform-entity-print-attachment--' . $webform_submission->getWebform()->id() . '-' . $webform_submission->id() . '.pdf'; - - // Save printable document. - $print_engine = $print_engine_manager->createSelectedInstance($element['#export_type']); - $temporary_file_path = $print_builder->savePrintable([$webform_submission], $print_engine, $scheme, $file_name); - if ($temporary_file_path) { - $contents = file_get_contents($temporary_file_path); - \Drupal::service('file_system')->delete($temporary_file_path); + $file_path = NULL; + + // If attachment with digital signatur, check if we already have one. + if (isset($element['#digital_signature']) && $element['#digital_signature']) { + // Get scheme. + $scheme = 'private'; + + // Get filename. + $file_name = 'webform/' . $webform_submission->getWebform()->id() . '/digital_signature/' . $submissionUuid . '.pdf'; + $file_path = "$scheme://$file_name"; + } + + if (!$file_path || !file_exists($file_path)) { + // Get scheme. + $scheme = 'temporary'; + // Get filename. + $file_name = 'webform-entity-print-attachment--' . $webform_submission->getWebform()->id() . '-' . $webform_submission->id() . '.pdf'; + + // Save printable document. + $print_engine = $print_engine_manager->createSelectedInstance($element['#export_type']); + $file_path = $print_builder->savePrintable([$webform_submission], $print_engine, $scheme, $file_name); + } + + if ($file_path) { + $contents = file_get_contents($file_path); + + // Deleting temporary file. + if ($scheme == 'temporary') { + \Drupal::service('file_system')->delete($file_path); + } } else { // Log error. diff --git a/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php b/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php index 4f2215d6..7ec580bd 100644 --- a/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php +++ b/modules/os2forms_attachment/src/Plugin/WebformElement/AttachmentElement.php @@ -27,6 +27,7 @@ protected function defineDefaultProperties() { 'view_mode' => 'html', 'template' => '', 'export_type' => '', + 'digital_signature' => '', 'exclude_empty' => '', 'exclude_empty_checkbox' => '', 'excluded_elements' => '', @@ -88,6 +89,11 @@ public function form(array $form, FormStateInterface $form_state) { 'html' => $this->t('HTML'), ], ]; + $form['attachment']['digital_signature'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Digital signature'), + ]; + // Set #access so that help is always visible. WebformElementHelper::setPropertyRecursive($form['attachment']['help'], '#access', TRUE); diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml new file mode 100644 index 00000000..d744350b --- /dev/null +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml @@ -0,0 +1,9 @@ +name: 'OS2Forms Digital Signature' +type: module +description: 'todo' +package: 'OS2Forms' +core_version_requirement: ^9 || ^10 +dependencies: + - 'webform:webform' + +configure: os2forms_digital_post.admin.settings diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.module b/modules/os2forms_digital_signature/os2forms_digital_signature.module new file mode 100644 index 00000000..025c20da --- /dev/null +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.module @@ -0,0 +1,28 @@ +getFormObject()->getEntity(); + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $webformSubmission->getWebform(); + + // Checking for os2forms_digital_signature handler presence. + foreach ($webform->getHandlers()->getConfiguration() as $handlerConf) { + if ($handlerConf['id'] == 'os2forms_digital_signature') { + $config = \Drupal::config('webform.settings'); + $settings = $config->get('settings'); + + // Checking if the title has not been overridden. + if ($settings['default_submit_button_label'] == $form['actions']['submit']['#value']){ + $form['actions']['submit']['#value'] = t('Sign and submit'); + } + } + } +} diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml new file mode 100644 index 00000000..54e01a74 --- /dev/null +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml @@ -0,0 +1,18 @@ +# Webform os2forms_attachment_component routes. +os2forms_digital_signature.sign_callback: + path: '/os2forms_digital_signature/{uuid}/{hash}/sign_callback' + defaults: + _controller: '\Drupal\os2forms_digital_signature\Controller\DigitalSignatureController::signCallback' + requirements: + _permission: 'access content' + +os2forms_digital_signature.test: + path: '/os2forms_digital_signature/{webform_submission}/test' + defaults: + _controller: '\Drupal\os2forms_digital_signature\Controller\DigitalSignatureController::test' +# _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' +# view_mode: 'html' +# operation: webform_submission_view +# entity_access: 'webform_submission.view' + requirements: + _permission: 'access content' diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml new file mode 100644 index 00000000..fa1587e6 --- /dev/null +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml @@ -0,0 +1,4 @@ +services: + os2forms_digital_signature.signing_service: + class: Drupal\os2forms_digital_signature\Service\SigningService + arguments: [] diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php new file mode 100644 index 00000000..8e2ebaf4 --- /dev/null +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -0,0 +1,139 @@ +getStorage('webform_submission') + ->loadByProperties(['uuid' => $uuid]); + + // Since loadByProperties returns an array, we need to fetch the first item. + $webformSubmission = $submissions ? reset($submissions) : NULL; + if (!$webformSubmission) { + throw new NotFoundHttpException(); + } + + $webformId = $webformSubmission->getWebform()->id(); + + // Checking hash. + $salt = \Drupal::service('settings')->get('hash_salt'); + $tmpHash = Crypt::hashBase64($uuid . $webformId . $salt); + if ($hash !== $tmpHash) { + throw new NotFoundHttpException(); + } + + /** @var SigningService $signingService */ + $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); + + $signeFilename = \Drupal::request()->get('file'); + $signedFileContent = $signingService->download($signeFilename); + if (!$signedFileContent) { + throw new NotFoundHttpException(); + } + + // Prepare the directory to ensure it exists and is writable. + $file_system = \Drupal::service('file_system'); + $expectedFileUri = "private://webform/$webformId/digital_signature/$uuid.pdf"; + $directory = dirname($expectedFileUri); + + if (!$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { + // TODO: throw error. + //\Drupal::logger('my_module')->error('Failed to prepare directory %directory.', ['%directory' => $directory]); + } + + // Write the data to the file using Drupal's file system service. + try { + $file_system->saveData($signedFileContent, $expectedFileUri , FileSystemInterface::EXISTS_REPLACE); + } + catch (\Exception $e) { + // TODO: throw error. + //\Drupal::logger('my_module')->error('Failed to write to file %uri: @message', ['%uri' => $uri, '@message' => $e->getMessage()]); + } + + // Updating webform submission. + $webformSubmission->setLocked(TRUE); + $webformSubmission->save(); + + // Build the URL for the webform submission confirmation page. + $confirmation_url = Url::fromRoute('entity.webform.confirmation', [ + 'webform' => $webformId, + 'webform_submission' => $webformSubmission->id(), + ])->toString(); + + // Redirect to the webform confirmation page. + $response = new RedirectResponse($confirmation_url); + return $response; + } + + + + + public function test(WebformSubmission $webform_submission) { + $webformId = $webform_submission->getWebform()->id(); + $sid = $webform_submission->id(); + + $fileUri = "private://webform/$webformId/digital_signature/$sid.pdf"; +// $webform_submission->resave(); +// dpm('Done'); + + /** @var SigningService $signingService */ + $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); + + $signeFilename = \Drupal::request()->get('file'); + $signedFileContent = $signingService->download($signeFilename); + + // Get the FileSystem service. + $file_system = \Drupal::service('file_system'); + + // Prepare the directory to ensure it exists and is writable. + $directory = dirname($fileUri); + if (!$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { + //\Drupal::logger('my_module')->error('Failed to prepare directory %directory.', ['%directory' => $directory]); + } + + // Write the data to the file using Drupal's file system service. + try { + $result = $file_system->saveData($signedFileContent, $fileUri , FileSystemInterface::EXISTS_REPLACE); + } + catch (\Exception $e) { + //\Drupal::logger('my_module')->error('Failed to write to file %uri: @message', ['%uri' => $uri, '@message' => $e->getMessage()]); + } + + $webform_submission->setLocked(TRUE); + $webform_submission->save(); + + // Build the URL for the webform confirmation page. + $confirmation_url = Url::fromRoute('entity.webform.confirmation', [ + 'webform' => $webformId, + 'webform_submission' => $sid, + ])->toString(); + + // Redirect to the webform confirmation page. + $response = new RedirectResponse($confirmation_url); + return $response; + } +} diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php new file mode 100644 index 00000000..9e9d7cd3 --- /dev/null +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -0,0 +1,293 @@ +renderer = $container->get('renderer'); + $instance->moduleHandler = $container->get('module_handler'); + $instance->elementManager = $container->get('plugin.manager.webform.element'); + return $instance; + } + +// /** +// * {@inheritdoc} +// */ +// public function defaultConfiguration() { +// return [ +// 'format' => 'yaml', +// 'submission' => FALSE, +// ]; +// } + +// /** +// * {@inheritdoc} +// */ +// public function getSummary() { +// $settings = $this->getSettings(); +// switch ($settings['format']) { +// case static::FORMAT_JSON: +// $settings['format'] = $this->t('JSON'); +// break; +// +// case static::FORMAT_YAML: +// default: +// $settings['format'] = $this->t('YAML'); +// break; +// } +// return [ +// '#settings' => $settings, +// ] + parent::getSummary(); +// } +// +// /** +// * {@inheritdoc} +// */ +// public function buildConfigurationForm(array $form, FormStateInterface $form_state) { +// $form['debug_settings'] = [ +// '#type' => 'fieldset', +// '#title' => $this->t('Debug settings'), +// ]; +// $form['debug_settings']['format'] = [ +// '#type' => 'select', +// '#title' => $this->t('Data format'), +// '#options' => [ +// static::FORMAT_YAML => $this->t('YAML'), +// static::FORMAT_JSON => $this->t('JSON'), +// ], +// '#default_value' => $this->configuration['format'], +// ]; +// $form['debug_settings']['submission'] = [ +// '#type' => 'checkbox', +// '#title' => $this->t('Include submission properties'), +// '#description' => $this->t('If checked, all submission properties and values will be included in the displayed debug information. This includes sid, created, updated, completed, and more.'), +// '#return_value' => TRUE, +// '#default_value' => $this->configuration['submission'], +// ]; +// return $this->setSettingsParents($form); +// } + +// /** +// * {@inheritdoc} +// */ +// public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { +// parent::submitConfigurationForm($form, $form_state); +// $this->applyFormStateToConfiguration($form_state); +// } +// +// /** +// * {@inheritdoc} +// */ +// public function submitForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { +// $settings = $this->getSettings(); +// +// $data = ($settings['submission']) +// ? $webform_submission->toArray(TRUE) +// : $webform_submission->getData(); +// WebformElementHelper::convertRenderMarkupToStrings($data); +// +// $label = ($settings['submission']) +// ? $this->t('Submitted properties and values are:') +// : $this->t('Submitted values are:'); +// +// $build = [ +// 'label' => ['#markup' => $label], +// 'data' => [ +// '#markup' => ($settings['format'] === static::FORMAT_JSON) +// ? json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_PRETTY_PRINT) +// : WebformYaml::encode($data), +// '#prefix' => '
',
+//        '#suffix' => '
', +// ], +// ]; +// $message = $this->renderer->renderPlain($build); +// +// $this->messenger()->addWarning($message); +// } + + /** + * {@inheritdoc} + */ + public function preSave(WebformSubmissionInterface $webform_submission) { + if ($webform_submission->isLocked()) { + return; + } + + $attachments = $this->getSubmissionAttachment($webform_submission); + //$destination = 'private://webform/signing' . $webform_submission->uuid() .'.pdf'; + //$pdfToSign = file_put_contents($destination, $attachment['filecontent'], FILE_APPEND); + + // TODO: think about file URL protection. + $destinationDir = 'public://signing'; + \Drupal::service('file_system')->prepareDirectory($destinationDir, FileSystemInterface::CREATE_DIRECTORY); + + $destination = $destinationDir . '/' . $webform_submission->uuid() .'.pdf'; + + // Save the file data. + /** @var FileInterface $fileSubmissionPdf */ + $fileSubmissionPdf = \Drupal::service('file.repository')->writeData($attachments[0]['filecontent'], $destination, FileSystemInterface::EXISTS_REPLACE); + + if ($fileSubmissionPdf) { + // Set the status to permanent to prevent file deletion on cron. + //$fileSubmissionPdf->setPermanent(); + + $fileSubmissionPdf->save(); + $submissionPdfPublicUrl = \Drupal::service('file_url_generator')->generateAbsoluteString($fileSubmissionPdf->getFileUri()); + } + + if ($submissionPdfPublicUrl) { + // For testing. + //$submissionPdfPublicUrl = 'https://signering.bellcom.dk/test/test-form.pdf'; + + /** @var SigningService $signingService */ + $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); + + $cid = $signingService->get_cid(); + if (empty($cid)) { + die('Failed to obtain cid. Is server running?'); + } + + // Creating hash. + $salt = \Drupal::service('settings')->get('hash_salt'); + $hash = Crypt::hashBase64($webform_submission->uuid() . $webform_submission->getWebform()->id() . $salt); + + $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash]); + + // Starting signing + $signingService->sign($submissionPdfPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); + } + } + + + public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { + return; + + if ($webform_submission->isLocked()) { + return; + } +// Getting attachments. + $attachments = $this->getMessageAttachments($webform_submission); + dpm($attachments); + return; +// +// // Getting attachment as file. TODO: is there a better way to do it? +// $data = $attachments[0]['filecontent']; +// $destination = 'sites/default/files/teststan' . $webform_submission->id() .'.pdf'; + $submissionPdfPublicUrl = 'https://signering.bellcom.dk/test/test-form.pdf'; + + +// // Write data to the file. +// $result = file_put_contents($destination, $data, FILE_APPEND); +//// $response = \Drupal::httpClient()->get($url, ['sink' => $destination]); +// + /** @var SigningService $signingService */ + $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); + + $cid = $signingService->get_cid(); + if(empty($cid)) { + die('Failed to obtain cid. Is server running?'); + } + + $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.test', ['webform_submission' => $webform_submission->id()]); + + // Starting signing + $signingService->sign($submissionPdfPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); + + // Making redirect. +// $response = new RedirectResponse('https://google.com'); +// $response->send(); + +// $response = new RedirectResponse($url->setAbsolute()->toString()); +// $response->send(); + +// $webform_submission->resave(); + } + + /** + * Get OS2forms file attachment. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return array|null + * Array of attachment data. + */ + protected function getSubmissionAttachment(WebformSubmissionInterface $webform_submission) { + $attachment = NULL; + $elements = $this->getWebform()->getElementsInitializedAndFlattened(); + $element_attachments = $this->getWebform()->getElementsAttachments(); + foreach ($element_attachments as $element_attachment) { + // Check if the element attachment key is excluded and should not attach any files. + if (isset($this->configuration['excluded_elements'][$element_attachment])) { + continue; + } + + $element = $elements[$element_attachment]; + if ($element['#type'] == 'os2forms_attachment') { + /** @var \Drupal\webform\Plugin\WebformElementAttachmentInterface $element_plugin */ + $element_plugin = $this->elementManager->getElementInstance($element); + $attachment = $element_plugin->getEmailAttachments($element, $webform_submission); + } + } + + // For SwiftMailer && Mime Mail use filecontent and not the filepath. + // @see \Drupal\swiftmailer\Plugin\Mail\SwiftMailer::attachAsMimeMail + // @see \Drupal\mimemail\Utility\MimeMailFormatHelper::mimeMailFile + // @see https://www.drupal.org/project/webform/issues/3232756 + if ($this->moduleHandler->moduleExists('swiftmailer') + || $this->moduleHandler->moduleExists('mimemail')) { + if (isset($attachment['filecontent']) && isset($attachment['filepath'])) { + unset($attachment['filepath']); + } + } + + return $attachment; + } + +} diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php new file mode 100644 index 00000000..31b3dc5b --- /dev/null +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -0,0 +1,156 @@ +SIGN_REMOTE_SERVICE_URL . 'action=getcid'; + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + $result = curl_exec($curl); + + $this->reply = json_decode($result, JSON_OBJECT_AS_ARRAY); + + return $this->reply['cid'] ?? NULL; + } + + /** + * Sign the document. + * + * Signing is done by redirecting the user's browser to a url on the signing server that takes the user + * through the signing flow. + * + * This function will never return. + * + * @param string $document_uri + * A uri to a file on the local server that we want to sign or the file name on the signing server in the SIGN_PDF_UPLOAD_DIR. + * In case of a local file, it must be prefixed by 'http://' or 'https://' and be readable from the signing server. + * @param string $cid + * The cid made available by the get_cid() function. + * @param string $forward_url + * The url on the local server to forward user to afterwards. + * @param bool $leave + * Leave the pdf file on the remote server. + * + * @throws SignParameterException + * Empty url or cid given. + */ + public function sign(string $document_uri, string $cid, string $forward_url, bool $leave = FALSE) { + if(empty($document_uri) || empty($cid) || empty($forward_url)) { + //throw new SignParameterException(); + } + + $hash = SigningUtil::get_hash($forward_url); + $params = ['action' => 'sign', 'cid' => $cid, 'hash' => $hash, 'uri' => base64_encode($document_uri), 'forward_url' => base64_encode($forward_url)]; + $url = $this->SIGN_REMOTE_SERVICE_URL . http_build_query($params); + + SigningUtil::url_forward($url); + } + + /** + * Verify the document. + * + * Verifying is done by redirecting the user's browser to a url on the signing server that takes the user + * through the verify flow. + * + * This function will never return. + * + * @param string $forward_url + * A url to a file on the local server that we want to sign or the full file name on the signing server. + * In case of a local file, it must be prefixed by 'http://' or 'https://' and be readable from the signing server. + * + * @throws SignParameterException + * Empty url or cid given. + * + * @todo Verifying the pdf is yet to be implemented on the signing server. + */ + public function verify(string $document_uri, string $cid, string $forward_url) { + SigningUtil::logger('Verify unimplemented!', 'WARNING'); + if(empty($forward_url)) { + //throw new SignParameterException(); + } + + $hash = SigningUtil::get_hash($forward_url); + $params = ['action' => 'verify', 'hash' => $hash, 'uri' => base64_encode($document_uri), 'forward_url' => base64_encode($forward_url)]; + $url = $this->SIGN_REMOTE_SERVICE_URL . http_build_query($params); + + SigningUtil::url_forward($url); + } + + /** + * Download the pdf file and return it as a binary string. + * + * @param string $filename + * The filename as given by the signing server. + * @param boolean $leave + * If TRUE, leave the file on the remote server, default is to remove the file after download. + * + * @return mixed + * The binary data of the pdf or an array if an error occured. + */ + public function download(string $filename, $leave = FALSE) { + if (empty($filename)) { + return FALSE; + //throw new SignParameterException('Filename cannot be empty'); + } + if (!preg_match('/^[a-f0-9]{32}\.pdf$/', $filename)) { + return FALSE; + //throw new SignParameterException('Incorrect filename given'); + } + $params = ['action' => 'download', 'file' => $filename, 'leave' => $leave]; + $url = $this->SIGN_REMOTE_SERVICE_URL . http_build_query($params); + + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + $return = curl_exec($curl); + + if (empty($return)) { + return FALSE; + //$return = ['error' => TRUE, 'message' => 'Empty file']; + } + elseif (substr($return, 0, 5) !== '%PDF-') { + return FALSE; + //$return = ['error' => TRUE, 'message' => 'Not a PDF file']; + } + + return $return; + } + + /** + * Download the pdf file and send it to the user's browser. + * + * @param string $filename + * The filename. + * + * @throws SignException + */ + public function view(string $filename) { + $pdf = $this->download($filename); + if(is_array($pdf)) { + print 'Unable to view file: ' . $pdf['message']; + return; + } + + header('Content-Type: application/pdf'); + header('Content-Length: ' . strlen($pdf)); + + print $pdf; + } +} diff --git a/modules/os2forms_digital_signature/src/Service/SigningUtil.php b/modules/os2forms_digital_signature/src/Service/SigningUtil.php new file mode 100644 index 00000000..89475a72 --- /dev/null +++ b/modules/os2forms_digital_signature/src/Service/SigningUtil.php @@ -0,0 +1,93 @@ +send(); + +// header("location: $url"); +// print "\n"; +// +// die(); + } + + /** + * Write a message to the log file. + * + * @param string $message + * The message to write. + * @param string $type + * One of 'INFO', 'WARNING' or 'ERROR'. + */ + public static function logger(string $message, string $type = 'INFO') { + if(SIGN_LOG_LEVEL == 'NONE') { + return; + } + + $type = in_array($type, ['INFO', 'WARNING', 'ERROR']) ? $type : 'INFO'; + $date = date('Y-m-d H:i:s'); + error_log("$date $type $message\n", 3, SIGN_LOGFILE); + } + + /** + * Takes a pathname and makes sure it ends with a slash. + * This is suitable for paths defined in the config.php file which may or may not end with a slash. + * + * @param string $path + * The path, e.g., '/tmp/' or '/tmp'. + * + * @return string + * The string with a slash suffix. + */ + public static function add_slash(string $path = '/') : string { + return rtrim($path, '/\\') . DIRECTORY_SEPARATOR; + } +} From 987b6ac6d80ee2dc9ab78167595088f5610897a8 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Tue, 15 Oct 2024 17:44:12 +0300 Subject: [PATCH 02/88] OS-110 code refactoring, security --- modules/os2forms_digital_signature/README.md | 40 ++++ .../os2forms_digital_signature.links.menu.yml | 5 + .../os2forms_digital_signature.module | 33 +++ .../os2forms_digital_signature.routing.yml | 15 +- .../os2forms_digital_signature.services.yml | 2 +- .../Controller/DigitalSignatureController.php | 65 +----- .../src/Form/SettingsForm.php | 74 ++++++ .../DigitalSignatureWebformHandler.php | 211 ++++-------------- .../src/Service/SigningService.php | 107 +++------ .../src/Service/SigningUtil.php | 93 -------- 10 files changed, 244 insertions(+), 401 deletions(-) create mode 100644 modules/os2forms_digital_signature/README.md create mode 100644 modules/os2forms_digital_signature/os2forms_digital_signature.links.menu.yml create mode 100644 modules/os2forms_digital_signature/src/Form/SettingsForm.php delete mode 100644 modules/os2forms_digital_signature/src/Service/SigningUtil.php diff --git a/modules/os2forms_digital_signature/README.md b/modules/os2forms_digital_signature/README.md new file mode 100644 index 00000000..1217d6e9 --- /dev/null +++ b/modules/os2forms_digital_signature/README.md @@ -0,0 +1,40 @@ +# OS2Forms Digital Signature module + +## Module purpose + +This module provides functionality for adding digital signature to the webform PDF submissions. + +## How does it work + +### Activating Digital Signature + +1. Add the OS2forms attachment element to the form. +2. Indicate that the OS2Forms attachment requires a digital signature. +3. Add the Digital Signature Handler to the webform. +4. If the form requires an email handler, ensure the trigger is set to **...when submission is locked** in the handler’s *Additional settings*. + +### Flow Explained + +1. Upon form submission, a PDF is generated, saved in the private directory, and sent to the signature service via URL. +2. The user is redirected to the signature service to provide their signature. +3. After signing, the user is redirected back to the webform solution. +4. The signed PDF is downloaded and stored in Drupal’s private directory. +5. When a submission PDF is requested (e.g., via download link or email), the signed PDF is served instead of generating a new one on the fly. + +## Settings page + +URL: `admin/os2forms_digital_signature/settings` + +- **Signature server URL** + + The URL of the service providing digital signature. This is the example of a known service https://signering.bellcom.dk/sign.php? + + +- **Hash Salt used for signature** + + Must match hash salt on the signature server + + +- **List IP's which can download unsigned PDF submissions** + + Only requests from this IP will be able to download PDF which are to be signed. diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.links.menu.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.links.menu.yml new file mode 100644 index 00000000..2fc07d22 --- /dev/null +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.links.menu.yml @@ -0,0 +1,5 @@ +os2forms_digital_signature.admin.settings: + title: OS2Forms digital signature + description: Configure the OS2Forms digital signature module + parent: system.admin_config_system + route_name: os2forms_digital_signature.settings diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.module b/modules/os2forms_digital_signature/os2forms_digital_signature.module index 025c20da..1f686baf 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.module +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.module @@ -1,6 +1,8 @@ get('os2forms_digital_signature_submission_allowed_ips'); + + $allowedIpsArr = explode(',', $allowedIps); + $remoteIp = Drupal::request()->getClientIp(); + + // IP list is empty, or request IP is allowed. + if (empty($allowedIpsArr) || in_array($remoteIp, $allowedIpsArr)) { + $basename = basename($uri); + return [ + 'Content-disposition' => 'attachment; filename="' . $basename . '"', + ]; + } + + // Otherwise - Deny access. + return -1; + } + + // Not submission file, allow normal access. + return NULL; +} diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml index 54e01a74..227a3a5d 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml @@ -5,14 +5,11 @@ os2forms_digital_signature.sign_callback: _controller: '\Drupal\os2forms_digital_signature\Controller\DigitalSignatureController::signCallback' requirements: _permission: 'access content' - -os2forms_digital_signature.test: - path: '/os2forms_digital_signature/{webform_submission}/test' +os2forms_digital_signature.settings: + path: '/admin/os2forms_digital_signature/settings' defaults: - _controller: '\Drupal\os2forms_digital_signature\Controller\DigitalSignatureController::test' -# _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' -# view_mode: 'html' -# operation: webform_submission_view -# entity_access: 'webform_submission.view' + _form: '\Drupal\os2forms_digital_signature\Form\SettingsForm' + _title: 'Digital signature settings' requirements: - _permission: 'access content' + _permission: 'administer site configuration' + diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml index fa1587e6..d5d1b220 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml @@ -1,4 +1,4 @@ services: os2forms_digital_signature.signing_service: class: Drupal\os2forms_digital_signature\Service\SigningService - arguments: [] + arguments: ['@config.factory'] diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 8e2ebaf4..6f4db014 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -6,7 +6,6 @@ use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Url; use Drupal\os2forms_digital_signature\Service\SigningService; -use Drupal\webform\Entity\WebformSubmission; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -34,6 +33,7 @@ public function signCallback($uuid, $hash) { // Since loadByProperties returns an array, we need to fetch the first item. $webformSubmission = $submissions ? reset($submissions) : NULL; if (!$webformSubmission) { + // Submission does not exist. throw new NotFoundHttpException(); } @@ -43,6 +43,7 @@ public function signCallback($uuid, $hash) { $salt = \Drupal::service('settings')->get('hash_salt'); $tmpHash = Crypt::hashBase64($uuid . $webformId . $salt); if ($hash !== $tmpHash) { + // Submission exist, but the provided hash is incorrect. throw new NotFoundHttpException(); } @@ -52,6 +53,7 @@ public function signCallback($uuid, $hash) { $signeFilename = \Drupal::request()->get('file'); $signedFileContent = $signingService->download($signeFilename); if (!$signedFileContent) { + \Drupal::logger('os2forms_digital_signature')->warning('Missing file on remote server %file.', ['%file' => $signeFilename]); throw new NotFoundHttpException(); } @@ -61,23 +63,21 @@ public function signCallback($uuid, $hash) { $directory = dirname($expectedFileUri); if (!$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { - // TODO: throw error. - //\Drupal::logger('my_module')->error('Failed to prepare directory %directory.', ['%directory' => $directory]); + \Drupal::logger('os2forms_digital_signature')->error('Failed to prepare directory %directory.', ['%directory' => $directory]); } // Write the data to the file using Drupal's file system service. try { $file_system->saveData($signedFileContent, $expectedFileUri , FileSystemInterface::EXISTS_REPLACE); + + // Updating webform submission. + $webformSubmission->setLocked(TRUE); + $webformSubmission->save(); } catch (\Exception $e) { - // TODO: throw error. - //\Drupal::logger('my_module')->error('Failed to write to file %uri: @message', ['%uri' => $uri, '@message' => $e->getMessage()]); + \Drupal::logger('os2forms_digital_signature')->error('Failed to write to file %uri: @message', ['%uri' => $expectedFileUri, '@message' => $e->getMessage()]); } - // Updating webform submission. - $webformSubmission->setLocked(TRUE); - $webformSubmission->save(); - // Build the URL for the webform submission confirmation page. $confirmation_url = Url::fromRoute('entity.webform.confirmation', [ 'webform' => $webformId, @@ -89,51 +89,4 @@ public function signCallback($uuid, $hash) { return $response; } - - - - public function test(WebformSubmission $webform_submission) { - $webformId = $webform_submission->getWebform()->id(); - $sid = $webform_submission->id(); - - $fileUri = "private://webform/$webformId/digital_signature/$sid.pdf"; -// $webform_submission->resave(); -// dpm('Done'); - - /** @var SigningService $signingService */ - $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); - - $signeFilename = \Drupal::request()->get('file'); - $signedFileContent = $signingService->download($signeFilename); - - // Get the FileSystem service. - $file_system = \Drupal::service('file_system'); - - // Prepare the directory to ensure it exists and is writable. - $directory = dirname($fileUri); - if (!$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { - //\Drupal::logger('my_module')->error('Failed to prepare directory %directory.', ['%directory' => $directory]); - } - - // Write the data to the file using Drupal's file system service. - try { - $result = $file_system->saveData($signedFileContent, $fileUri , FileSystemInterface::EXISTS_REPLACE); - } - catch (\Exception $e) { - //\Drupal::logger('my_module')->error('Failed to write to file %uri: @message', ['%uri' => $uri, '@message' => $e->getMessage()]); - } - - $webform_submission->setLocked(TRUE); - $webform_submission->save(); - - // Build the URL for the webform confirmation page. - $confirmation_url = Url::fromRoute('entity.webform.confirmation', [ - 'webform' => $webformId, - 'webform_submission' => $sid, - ])->toString(); - - // Redirect to the webform confirmation page. - $response = new RedirectResponse($confirmation_url); - return $response; - } } diff --git a/modules/os2forms_digital_signature/src/Form/SettingsForm.php b/modules/os2forms_digital_signature/src/Form/SettingsForm.php new file mode 100644 index 00000000..473f64ec --- /dev/null +++ b/modules/os2forms_digital_signature/src/Form/SettingsForm.php @@ -0,0 +1,74 @@ + 'textfield', + '#title' => t("Signature server URL"), + '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_remove_service_url'), + '#description' => t('E.g. https://signering.bellcom.dk/sign.php?'), + ]; + $form['os2forms_digital_signature_sign_hash_salt'] = [ + '#type' => 'textfield', + '#title' => t("Hash Salt used for signature"), + '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_sign_hash_salt'), + '#description' => t('Must match hash salt on the signature server'), + ]; + $form['os2forms_digital_signature_submission_allowed_ips'] = [ + '#type' => 'textfield', + '#title' => t("List IP's which can download unsigned PDF submissions"), + '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_submission_allowed_ips'), + '#description' => t('Comma separated. Ex. 192.168.1.1,192.168.2.1'), + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + + $config = $this->config(self::$configName); + foreach ($values as $key => $value) { + $config->set($key, $value); + } + $config->save(); + + parent::submitForm($form, $form_state); + } +} diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index 9e9d7cd3..d0c4b4a8 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -4,17 +4,12 @@ use Drupal\Component\Utility\Crypt; use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\file\FileInterface; use Drupal\os2forms_digital_signature\Service\SigningService; use Drupal\webform\Plugin\WebformHandlerBase; -use Drupal\webform\Utility\WebformElementHelper; -use Drupal\webform\Utility\WebformYaml; use Drupal\webform\WebformSubmissionInterface; -use phpseclib3\Crypt\Hash; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; /** * Webform submission debug handler. @@ -50,203 +45,68 @@ class DigitalSignatureWebformHandler extends WebformHandlerBase { */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); -// $instance->renderer = $container->get('renderer'); $instance->moduleHandler = $container->get('module_handler'); $instance->elementManager = $container->get('plugin.manager.webform.element'); return $instance; } -// /** -// * {@inheritdoc} -// */ -// public function defaultConfiguration() { -// return [ -// 'format' => 'yaml', -// 'submission' => FALSE, -// ]; -// } - -// /** -// * {@inheritdoc} -// */ -// public function getSummary() { -// $settings = $this->getSettings(); -// switch ($settings['format']) { -// case static::FORMAT_JSON: -// $settings['format'] = $this->t('JSON'); -// break; -// -// case static::FORMAT_YAML: -// default: -// $settings['format'] = $this->t('YAML'); -// break; -// } -// return [ -// '#settings' => $settings, -// ] + parent::getSummary(); -// } -// -// /** -// * {@inheritdoc} -// */ -// public function buildConfigurationForm(array $form, FormStateInterface $form_state) { -// $form['debug_settings'] = [ -// '#type' => 'fieldset', -// '#title' => $this->t('Debug settings'), -// ]; -// $form['debug_settings']['format'] = [ -// '#type' => 'select', -// '#title' => $this->t('Data format'), -// '#options' => [ -// static::FORMAT_YAML => $this->t('YAML'), -// static::FORMAT_JSON => $this->t('JSON'), -// ], -// '#default_value' => $this->configuration['format'], -// ]; -// $form['debug_settings']['submission'] = [ -// '#type' => 'checkbox', -// '#title' => $this->t('Include submission properties'), -// '#description' => $this->t('If checked, all submission properties and values will be included in the displayed debug information. This includes sid, created, updated, completed, and more.'), -// '#return_value' => TRUE, -// '#default_value' => $this->configuration['submission'], -// ]; -// return $this->setSettingsParents($form); -// } - -// /** -// * {@inheritdoc} -// */ -// public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { -// parent::submitConfigurationForm($form, $form_state); -// $this->applyFormStateToConfiguration($form_state); -// } -// -// /** -// * {@inheritdoc} -// */ -// public function submitForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { -// $settings = $this->getSettings(); -// -// $data = ($settings['submission']) -// ? $webform_submission->toArray(TRUE) -// : $webform_submission->getData(); -// WebformElementHelper::convertRenderMarkupToStrings($data); -// -// $label = ($settings['submission']) -// ? $this->t('Submitted properties and values are:') -// : $this->t('Submitted values are:'); -// -// $build = [ -// 'label' => ['#markup' => $label], -// 'data' => [ -// '#markup' => ($settings['format'] === static::FORMAT_JSON) -// ? json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_PRETTY_PRINT) -// : WebformYaml::encode($data), -// '#prefix' => '
',
-//        '#suffix' => '
', -// ], -// ]; -// $message = $this->renderer->renderPlain($build); -// -// $this->messenger()->addWarning($message); -// } - /** * {@inheritdoc} */ public function preSave(WebformSubmissionInterface $webform_submission) { + $webform = $webform_submission->getWebform(); + if ($webform_submission->isLocked()) { return; } - $attachments = $this->getSubmissionAttachment($webform_submission); - //$destination = 'private://webform/signing' . $webform_submission->uuid() .'.pdf'; - //$pdfToSign = file_put_contents($destination, $attachment['filecontent'], FILE_APPEND); + $attachment = $this->getSubmissionAttachment($webform_submission); + if (!$attachment) { + \Drupal::logger('os2forms_digital_signature')->error('Attachment cannot be created webform: %webform, webform_submission: %webform_submission', ['%webform' => $webform->id(), '%webform_submission' => $webform_submission->uuid()]); + return; + } // TODO: think about file URL protection. - $destinationDir = 'public://signing'; - \Drupal::service('file_system')->prepareDirectory($destinationDir, FileSystemInterface::CREATE_DIRECTORY); - - $destination = $destinationDir . '/' . $webform_submission->uuid() .'.pdf'; - - // Save the file data. - /** @var FileInterface $fileSubmissionPdf */ - $fileSubmissionPdf = \Drupal::service('file.repository')->writeData($attachments[0]['filecontent'], $destination, FileSystemInterface::EXISTS_REPLACE); - - if ($fileSubmissionPdf) { - // Set the status to permanent to prevent file deletion on cron. - //$fileSubmissionPdf->setPermanent(); - - $fileSubmissionPdf->save(); - $submissionPdfPublicUrl = \Drupal::service('file_url_generator')->generateAbsoluteString($fileSubmissionPdf->getFileUri()); + $destinationDir = 'private://signing'; + if (!\Drupal::service('file_system')->prepareDirectory($destinationDir, FileSystemInterface::CREATE_DIRECTORY)) { + \Drupal::logger('os2forms_digital_signature')->error('File directory cannot be created: %filedirectory', ['%filedirectory' => $destinationDir]); + return; } - if ($submissionPdfPublicUrl) { - // For testing. - //$submissionPdfPublicUrl = 'https://signering.bellcom.dk/test/test-form.pdf'; - - /** @var SigningService $signingService */ - $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); - - $cid = $signingService->get_cid(); - if (empty($cid)) { - die('Failed to obtain cid. Is server running?'); - } - - // Creating hash. - $salt = \Drupal::service('settings')->get('hash_salt'); - $hash = Crypt::hashBase64($webform_submission->uuid() . $webform_submission->getWebform()->id() . $salt); + $fileUri = $destinationDir . '/' . $webform_submission->uuid() .'.pdf'; - $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash]); - - // Starting signing - $signingService->sign($submissionPdfPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); + // Save the file data. + try { + /** @var FileInterface $fileSubmissionPdf */ + $fileSubmissionPdf = \Drupal::service('file.repository')->writeData($attachment['filecontent'], $fileUri, FileSystemInterface::EXISTS_REPLACE); } - } - - - public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { - return; - - if ($webform_submission->isLocked()) { + catch (\Exception $e) { + \Drupal::logger('os2forms_digital_signature')->error('File cannot be saved: %fileUri, error: %error', ['%fileUri' => $fileUri, '%error' => $e->getMessage()]); return; } -// Getting attachments. - $attachments = $this->getMessageAttachments($webform_submission); - dpm($attachments); - return; -// -// // Getting attachment as file. TODO: is there a better way to do it? -// $data = $attachments[0]['filecontent']; -// $destination = 'sites/default/files/teststan' . $webform_submission->id() .'.pdf'; - $submissionPdfPublicUrl = 'https://signering.bellcom.dk/test/test-form.pdf'; + // Set the status to permanent to prevent file deletion on cron. + //$fileSubmissionPdf->setPermanent(); + $fileSubmissionPdf->save(); + $submissionPdfPublicUrl = \Drupal::service('file_url_generator')->generateAbsoluteString($fileSubmissionPdf->getFileUri()); -// // Write data to the file. -// $result = file_put_contents($destination, $data, FILE_APPEND); -//// $response = \Drupal::httpClient()->get($url, ['sink' => $destination]); -// /** @var SigningService $signingService */ $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); $cid = $signingService->get_cid(); - if(empty($cid)) { - die('Failed to obtain cid. Is server running?'); + if (empty($cid)) { + \Drupal::logger('os2forms_digital_signature')->error('Failed to obtain cid. Is server running?'); + return; } - $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.test', ['webform_submission' => $webform_submission->id()]); + // Creating hash. + $salt = \Drupal::service('settings')->get('hash_salt'); + $hash = Crypt::hashBase64($webform_submission->uuid() . $webform->id() . $salt); - // Starting signing - $signingService->sign($submissionPdfPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); - - // Making redirect. -// $response = new RedirectResponse('https://google.com'); -// $response->send(); + $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash]); -// $response = new RedirectResponse($url->setAbsolute()->toString()); -// $response->send(); - -// $webform_submission->resave(); + // Starting signing, if everything is correct - this funcition will start redirect. + $signingService->sign($submissionPdfPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); } /** @@ -257,9 +117,12 @@ public function postSave(WebformSubmissionInterface $webform_submission, $update * * @return array|null * Array of attachment data. + * @throws \Exception */ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_submission) { + $attachments = NULL; $attachment = NULL; + $elements = $this->getWebform()->getElementsInitializedAndFlattened(); $element_attachments = $this->getWebform()->getElementsAttachments(); foreach ($element_attachments as $element_attachment) { @@ -272,10 +135,14 @@ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_s if ($element['#type'] == 'os2forms_attachment') { /** @var \Drupal\webform\Plugin\WebformElementAttachmentInterface $element_plugin */ $element_plugin = $this->elementManager->getElementInstance($element); - $attachment = $element_plugin->getEmailAttachments($element, $webform_submission); + $attachments = $element_plugin->getEmailAttachments($element, $webform_submission); } } + if (!empty($attachments)) { + $attachment = reset($attachments); + } + // For SwiftMailer && Mime Mail use filecontent and not the filepath. // @see \Drupal\swiftmailer\Plugin\Mail\SwiftMailer::attachAsMimeMail // @see \Drupal\mimemail\Utility\MimeMailFormatHelper::mimeMailFile diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index 31b3dc5b..674dae17 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -2,33 +2,39 @@ namespace Drupal\os2forms_digital_signature\Service; -class SigningService { - - private $reply = []; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; +use Drupal\os2forms_digital_signature\Form\SettingsForm; +use Symfony\Component\HttpFoundation\RedirectResponse; - private string $SIGN_REMOTE_SERVICE_URL = 'https://signering.bellcom.dk/sign.php?'; +class SigningService { /** - * Default constructor. + * The config. + * + * @var \Drupal\Core\Config\ImmutableConfig */ - public function __construct() { + private readonly ImmutableConfig $config; + + public function __construct(ConfigFactoryInterface $configFactory) { + $this->config = $configFactory->get(SettingsForm::$configName); } /** * Fetch a new cid. * - * @return string + * @return string|NULL * The correlation id. */ public function get_cid() : ?string { - $url = $this->SIGN_REMOTE_SERVICE_URL . 'action=getcid'; + $url = $this->config->get('os2forms_digital_signature_remove_service_url') . 'action=getcid'; $curl = curl_init($url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($curl); - $this->reply = json_decode($result, JSON_OBJECT_AS_ARRAY); + $reply = json_decode($result, JSON_OBJECT_AS_ARRAY); - return $this->reply['cid'] ?? NULL; + return $reply['cid'] ?? NULL; } /** @@ -49,49 +55,20 @@ public function get_cid() : ?string { * @param bool $leave * Leave the pdf file on the remote server. * - * @throws SignParameterException - * Empty url or cid given. + * @return void */ - public function sign(string $document_uri, string $cid, string $forward_url, bool $leave = FALSE) { - if(empty($document_uri) || empty($cid) || empty($forward_url)) { - //throw new SignParameterException(); + public function sign(string $document_uri, string $cid, string $forward_url, bool $leave = FALSE):void { + if (empty($document_uri) || empty($cid) || empty($forward_url)) { + \Drupal::logger('os2forms_digital_signature')->error('Cannot initiate signing process, check params: document_uri: %document_uri, cid: %cid, forward_url: %forward_url', ['%document_uri' => $document_uri, '%cid' => $cid, '%forward_url' => $forward_url]); + return; } - $hash = SigningUtil::get_hash($forward_url); + $hash = $this->getHash($forward_url); $params = ['action' => 'sign', 'cid' => $cid, 'hash' => $hash, 'uri' => base64_encode($document_uri), 'forward_url' => base64_encode($forward_url)]; - $url = $this->SIGN_REMOTE_SERVICE_URL . http_build_query($params); + $url = $this->config->get('os2forms_digital_signature_remove_service_url') . http_build_query($params); - SigningUtil::url_forward($url); - } - - /** - * Verify the document. - * - * Verifying is done by redirecting the user's browser to a url on the signing server that takes the user - * through the verify flow. - * - * This function will never return. - * - * @param string $forward_url - * A url to a file on the local server that we want to sign or the full file name on the signing server. - * In case of a local file, it must be prefixed by 'http://' or 'https://' and be readable from the signing server. - * - * @throws SignParameterException - * Empty url or cid given. - * - * @todo Verifying the pdf is yet to be implemented on the signing server. - */ - public function verify(string $document_uri, string $cid, string $forward_url) { - SigningUtil::logger('Verify unimplemented!', 'WARNING'); - if(empty($forward_url)) { - //throw new SignParameterException(); - } - - $hash = SigningUtil::get_hash($forward_url); - $params = ['action' => 'verify', 'hash' => $hash, 'uri' => base64_encode($document_uri), 'forward_url' => base64_encode($forward_url)]; - $url = $this->SIGN_REMOTE_SERVICE_URL . http_build_query($params); - - SigningUtil::url_forward($url); + $response = new RedirectResponse($url); + $response->send(); } /** @@ -102,20 +79,18 @@ public function verify(string $document_uri, string $cid, string $forward_url) { * @param boolean $leave * If TRUE, leave the file on the remote server, default is to remove the file after download. * - * @return mixed - * The binary data of the pdf or an array if an error occured. + * @return mixed|bool + * The binary data of the pdf or FALSE if an error occurred. */ public function download(string $filename, $leave = FALSE) { if (empty($filename)) { return FALSE; - //throw new SignParameterException('Filename cannot be empty'); } if (!preg_match('/^[a-f0-9]{32}\.pdf$/', $filename)) { return FALSE; - //throw new SignParameterException('Incorrect filename given'); } $params = ['action' => 'download', 'file' => $filename, 'leave' => $leave]; - $url = $this->SIGN_REMOTE_SERVICE_URL . http_build_query($params); + $url = $this->config->get('os2forms_digital_signature_remove_service_url') . http_build_query($params); $curl = curl_init($url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); @@ -123,34 +98,26 @@ public function download(string $filename, $leave = FALSE) { if (empty($return)) { return FALSE; - //$return = ['error' => TRUE, 'message' => 'Empty file']; } elseif (substr($return, 0, 5) !== '%PDF-') { return FALSE; - //$return = ['error' => TRUE, 'message' => 'Not a PDF file']; } return $return; } /** - * Download the pdf file and send it to the user's browser. + * Calculate the hash value. * - * @param string $filename - * The filename. + * @param string $name + * The value to hash including salt. * - * @throws SignException + * @return string + * The hash value (sha1). */ - public function view(string $filename) { - $pdf = $this->download($filename); - if(is_array($pdf)) { - print 'Unable to view file: ' . $pdf['message']; - return; - } - - header('Content-Type: application/pdf'); - header('Content-Length: ' . strlen($pdf)); - - print $pdf; + private function getHash(string $value) : string { + $hashSalt = $this->config->get('os2forms_digital_signature_sign_hash_salt'); + return sha1($hashSalt . $value); } + } diff --git a/modules/os2forms_digital_signature/src/Service/SigningUtil.php b/modules/os2forms_digital_signature/src/Service/SigningUtil.php deleted file mode 100644 index 89475a72..00000000 --- a/modules/os2forms_digital_signature/src/Service/SigningUtil.php +++ /dev/null @@ -1,93 +0,0 @@ -send(); - -// header("location: $url"); -// print "\n"; -// -// die(); - } - - /** - * Write a message to the log file. - * - * @param string $message - * The message to write. - * @param string $type - * One of 'INFO', 'WARNING' or 'ERROR'. - */ - public static function logger(string $message, string $type = 'INFO') { - if(SIGN_LOG_LEVEL == 'NONE') { - return; - } - - $type = in_array($type, ['INFO', 'WARNING', 'ERROR']) ? $type : 'INFO'; - $date = date('Y-m-d H:i:s'); - error_log("$date $type $message\n", 3, SIGN_LOGFILE); - } - - /** - * Takes a pathname and makes sure it ends with a slash. - * This is suitable for paths defined in the config.php file which may or may not end with a slash. - * - * @param string $path - * The path, e.g., '/tmp/' or '/tmp'. - * - * @return string - * The string with a slash suffix. - */ - public static function add_slash(string $path = '/') : string { - return rtrim($path, '/\\') . DIRECTORY_SEPARATOR; - } -} From 53fb77e1598e2931ebf6b12a099ee28097008ea9 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Wed, 16 Oct 2024 16:58:37 +0300 Subject: [PATCH 03/88] OS-110 upload file for signature --- .../src/Element/DigitalSignatureDocument.php | 20 ++++++++++ .../DigitalSignatureDocument.php | 39 +++++++++++++++++++ .../DigitalSignatureWebformHandler.php | 29 ++++++++++---- 3 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 modules/os2forms_digital_signature/src/Element/DigitalSignatureDocument.php create mode 100644 modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php diff --git a/modules/os2forms_digital_signature/src/Element/DigitalSignatureDocument.php b/modules/os2forms_digital_signature/src/Element/DigitalSignatureDocument.php new file mode 100644 index 00000000..5f27ff79 --- /dev/null +++ b/modules/os2forms_digital_signature/src/Element/DigitalSignatureDocument.php @@ -0,0 +1,20 @@ +t('PDF file for signature'); + return $formats; + } + + /** + * {@inheritdoc} + */ + protected function getFileExtensions(array $element = NULL) { + return 'pdf'; + } + +} diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index d0c4b4a8..9591b065 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -77,8 +77,8 @@ public function preSave(WebformSubmissionInterface $webform_submission) { // Save the file data. try { - /** @var FileInterface $fileSubmissionPdf */ - $fileSubmissionPdf = \Drupal::service('file.repository')->writeData($attachment['filecontent'], $fileUri, FileSystemInterface::EXISTS_REPLACE); + /** @var FileInterface $fileToSign */ + $fileToSign = \Drupal::service('file.repository')->writeData($attachment['filecontent'], $fileUri, FileSystemInterface::EXISTS_REPLACE); } catch (\Exception $e) { \Drupal::logger('os2forms_digital_signature')->error('File cannot be saved: %fileUri, error: %error', ['%fileUri' => $fileUri, '%error' => $e->getMessage()]); @@ -86,9 +86,9 @@ public function preSave(WebformSubmissionInterface $webform_submission) { } // Set the status to permanent to prevent file deletion on cron. - //$fileSubmissionPdf->setPermanent(); - $fileSubmissionPdf->save(); - $submissionPdfPublicUrl = \Drupal::service('file_url_generator')->generateAbsoluteString($fileSubmissionPdf->getFileUri()); + //$fileToSign->setPermanent(); + $fileToSign->save(); + $fileToSignPublicUrl = \Drupal::service('file_url_generator')->generateAbsoluteString($fileToSign->getFileUri()); /** @var SigningService $signingService */ $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); @@ -106,7 +106,7 @@ public function preSave(WebformSubmissionInterface $webform_submission) { $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash]); // Starting signing, if everything is correct - this funcition will start redirect. - $signingService->sign($submissionPdfPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); + $signingService->sign($fileToSignPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); } /** @@ -123,6 +123,19 @@ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_s $attachments = NULL; $attachment = NULL; + // Getting all element types that are added to the webform. + // + // Priority is the following: check for os2forms_digital_signature_document, is not found try serving + // os2forms_attachment + $elementTypes = array_column($this->getWebform()->getElementsDecodedAndFlattened(), '#type'); + $attachmentType = ''; + if (in_array('os2forms_digital_signature_document', $elementTypes)) { + $attachmentType = 'os2forms_digital_signature_document'; + } + elseif (in_array('os2forms_attachment', $elementTypes)) { + $attachmentType = 'os2forms_attachment'; + } + $elements = $this->getWebform()->getElementsInitializedAndFlattened(); $element_attachments = $this->getWebform()->getElementsAttachments(); foreach ($element_attachments as $element_attachment) { @@ -132,10 +145,12 @@ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_s } $element = $elements[$element_attachment]; - if ($element['#type'] == 'os2forms_attachment') { + + if ($element['#type'] == $attachmentType) { /** @var \Drupal\webform\Plugin\WebformElementAttachmentInterface $element_plugin */ $element_plugin = $this->elementManager->getElementInstance($element); $attachments = $element_plugin->getEmailAttachments($element, $webform_submission); + break; } } From ff96214ff3be65df1ec7b123525d31e5c3c8b93f Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Wed, 16 Oct 2024 18:49:36 +0300 Subject: [PATCH 04/88] Upload digital document --- .../os2forms_digital_signature.routing.yml | 3 +- .../Controller/DigitalSignatureController.php | 31 ++++++++++--- .../DigitalSignatureDocument.php | 44 +++++++++++++++++++ .../DigitalSignatureWebformHandler.php | 9 +++- 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml index 227a3a5d..c286ad3b 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml @@ -1,8 +1,9 @@ # Webform os2forms_attachment_component routes. os2forms_digital_signature.sign_callback: - path: '/os2forms_digital_signature/{uuid}/{hash}/sign_callback' + path: '/os2forms_digital_signature/{uuid}/{hash}/sign_callback/{fid}' defaults: _controller: '\Drupal\os2forms_digital_signature\Controller\DigitalSignatureController::signCallback' + fid: '' requirements: _permission: 'access content' os2forms_digital_signature.settings: diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 6f4db014..4c31d19c 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\Crypt; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Url; +use Drupal\file\Entity\File; use Drupal\os2forms_digital_signature\Service\SigningService; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -18,13 +19,18 @@ class DigitalSignatureController { * * @param $uuid * Webform submission UUID. + * @param $hash + * Hash to check if the request is authentic. + * @param $fid + * File to replace (optional). + * * @return RedirectResponse * Redirect response to form submission confirmation. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ - public function signCallback($uuid, $hash) { + public function signCallback($uuid, $hash, $fid = NULL) { // Load the webform submission entity by UUID. $submissions = \Drupal::entityTypeManager() ->getStorage('webform_submission') @@ -57,13 +63,21 @@ public function signCallback($uuid, $hash) { throw new NotFoundHttpException(); } - // Prepare the directory to ensure it exists and is writable. + /** @var FileSystemInterface $file_system */ $file_system = \Drupal::service('file_system'); - $expectedFileUri = "private://webform/$webformId/digital_signature/$uuid.pdf"; - $directory = dirname($expectedFileUri); - if (!$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { - \Drupal::logger('os2forms_digital_signature')->error('Failed to prepare directory %directory.', ['%directory' => $directory]); + // If $fid is present - we are replacing uploaded/managed file, otherwise creating a new one. + if ($fid) { + $file = File::load($fid); + $expectedFileUri = $file->getFileUri(); + } else { + // Prepare the directory to ensure it exists and is writable. + $expectedFileUri = "private://webform/$webformId/digital_signature/$uuid.pdf"; + $directory = dirname($expectedFileUri); + + if (!$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { + \Drupal::logger('os2forms_digital_signature')->error('Failed to prepare directory %directory.', ['%directory' => $directory]); + } } // Write the data to the file using Drupal's file system service. @@ -73,6 +87,11 @@ public function signCallback($uuid, $hash) { // Updating webform submission. $webformSubmission->setLocked(TRUE); $webformSubmission->save(); + + // If file existing, resave the file to update the size and etc. + if ($fid) { + File::load($fid)->save(); + } } catch (\Exception $e) { \Drupal::logger('os2forms_digital_signature')->error('Failed to write to file %uri: @message', ['%uri' => $expectedFileUri, '@message' => $e->getMessage()]); diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php b/modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php index 2b34ca10..843a63c9 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php @@ -3,6 +3,7 @@ namespace Drupal\os2forms_digital_signature\Plugin\WebformElement; use Drupal\webform\Plugin\WebformElement\WebformManagedFileBase; +use Drupal\webform\WebformSubmissionInterface; /** * Provides a 'os2forms_digital_signature_document' element. @@ -36,4 +37,47 @@ protected function getFileExtensions(array $element = NULL) { return 'pdf'; } + + /** + * {@inheritdoc} + */ + protected function formatHtmlItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $value = $this->getValue($element, $webform_submission, $options); + $file = $this->getFile($element, $value, $options); + + if (empty($file)) { + return ''; + } + + $format = $this->getItemFormat($element); + switch ($format) { + case 'basename': + case 'extension': + case 'data': + case 'id': + case 'mime': + case 'name': + case 'raw': + case 'size': + case 'url': + case 'value': + return $this->formatTextItem($element, $webform_submission, $options); + + case 'link': + return [ + '#theme' => 'file_link', + '#file' => $file, + ]; + + default: + return [ + '#theme' => 'webform_element_document_file', + '#element' => $element, + '#value' => $value, + '#options' => $options, + '#file' => $file, + ]; + } + } + } diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index 9591b065..2c35b03d 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -66,7 +66,6 @@ public function preSave(WebformSubmissionInterface $webform_submission) { return; } - // TODO: think about file URL protection. $destinationDir = 'private://signing'; if (!\Drupal::service('file_system')->prepareDirectory($destinationDir, FileSystemInterface::CREATE_DIRECTORY)) { \Drupal::logger('os2forms_digital_signature')->error('File directory cannot be created: %filedirectory', ['%filedirectory' => $destinationDir]); @@ -103,7 +102,8 @@ public function preSave(WebformSubmissionInterface $webform_submission) { $salt = \Drupal::service('settings')->get('hash_salt'); $hash = Crypt::hashBase64($webform_submission->uuid() . $webform->id() . $salt); - $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash]); + $attahchmentFid = $attachment['fid'] ?? NULL; + $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash, 'fid' => $attahchmentFid]); // Starting signing, if everything is correct - this funcition will start redirect. $signingService->sign($fileToSignPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); @@ -150,6 +150,11 @@ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_s /** @var \Drupal\webform\Plugin\WebformElementAttachmentInterface $element_plugin */ $element_plugin = $this->elementManager->getElementInstance($element); $attachments = $element_plugin->getEmailAttachments($element, $webform_submission); + + // If we are dealing with an uploaded file, attach the FID. + if ($fid = $webform_submission->getElementData($element_attachment)) { + $attachments[0]['fid'] = $fid; + } break; } } From 3be3b706f2bd360a6050853c0d5c061e92732b99 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Tue, 19 Nov 2024 16:38:35 +0200 Subject: [PATCH 05/88] OS-110 Signing service: Support for adding an extra page with signing info --- .../src/Service/SigningService.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index 674dae17..d72ee120 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -52,12 +52,10 @@ public function get_cid() : ?string { * The cid made available by the get_cid() function. * @param string $forward_url * The url on the local server to forward user to afterwards. - * @param bool $leave - * Leave the pdf file on the remote server. * * @return void */ - public function sign(string $document_uri, string $cid, string $forward_url, bool $leave = FALSE):void { + public function sign(string $document_uri, string $cid, string $forward_url):void { if (empty($document_uri) || empty($cid) || empty($forward_url)) { \Drupal::logger('os2forms_digital_signature')->error('Cannot initiate signing process, check params: document_uri: %document_uri, cid: %cid, forward_url: %forward_url', ['%document_uri' => $document_uri, '%cid' => $cid, '%forward_url' => $forward_url]); return; @@ -78,18 +76,23 @@ public function sign(string $document_uri, string $cid, string $forward_url, boo * The filename as given by the signing server. * @param boolean $leave * If TRUE, leave the file on the remote server, default is to remove the file after download. + * @param boolean $annotate + * If TRUE, download a pdf with an annotation page. + * @param array $attributes + * An array of pairs of prompts and values that will be added to the annotation box, e.g., + * ['IP' => $_SERVER['REMOTE_ADDR'], 'Region' => 'Capital Region Copenhagen']. * * @return mixed|bool * The binary data of the pdf or FALSE if an error occurred. */ - public function download(string $filename, $leave = FALSE) { + public function download(string $filename, $leave = FALSE, $annotate = TRUE, $attributes = []) { if (empty($filename)) { return FALSE; } if (!preg_match('/^[a-f0-9]{32}\.pdf$/', $filename)) { return FALSE; } - $params = ['action' => 'download', 'file' => $filename, 'leave' => $leave]; + $params = ['action' => 'download', 'file' => $filename, 'leave' => $leave, 'annotate' => $annotate, 'attributes' => $attributes]; $url = $this->config->get('os2forms_digital_signature_remove_service_url') . http_build_query($params); $curl = curl_init($url); From d06c8626be500d05c8f8118f213876e5dd39fb02 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Mon, 9 Dec 2024 15:06:09 +0200 Subject: [PATCH 06/88] OS-144 - adding return URL --- .../src/Controller/DigitalSignatureController.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 4c31d19c..aaf0979b 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -7,6 +7,7 @@ use Drupal\Core\Url; use Drupal\file\Entity\File; use Drupal\os2forms_digital_signature\Service\SigningService; +use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -37,6 +38,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { ->loadByProperties(['uuid' => $uuid]); // Since loadByProperties returns an array, we need to fetch the first item. + /** @var WebformSubmissionInterface $webformSubmission */ $webformSubmission = $submissions ? reset($submissions) : NULL; if (!$webformSubmission) { // Submission does not exist. @@ -45,6 +47,16 @@ public function signCallback($uuid, $hash, $fid = NULL) { $webformId = $webformSubmission->getWebform()->id(); + // Checking the action + $action = \Drupal::request()->query->get('name'); + if ($action == 'cancel') { + $cancelUrl = $webformSubmission->getWebform()->toUrl()->toString(); + + // Redirect to the webform confirmation page. + $response = new RedirectResponse($cancelUrl); + return $response; + } + // Checking hash. $salt = \Drupal::service('settings')->get('hash_salt'); $tmpHash = Crypt::hashBase64($uuid . $webformId . $salt); From 92c0ec40d4a8b19d63b35a38fa7db833432ef7f5 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 7 May 2024 14:26:01 +0200 Subject: [PATCH 07/88] Moved digital post settings into config. Added support for os2web_key to get certificate. # Conflicts: # CHANGELOG.md # composer.json # modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php # modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php --- .github/workflows/pr.yml | 31 +-- .gitignore | 2 - .markdownlint.jsonc | 13 + .markdownlintrc | 18 -- CHANGELOG.md | 3 + README.md | 20 +- composer.json | 68 +++-- modules/os2forms_digital_post/README.md | 4 + .../os2forms_digital_post.info.yml | 1 + .../os2forms_digital_post.install | 9 + .../os2forms_digital_post.services.yml | 6 +- .../src/Exception/InvalidSettingException.php | 10 - .../src/Form/SettingsForm.php | 238 +++++++----------- .../src/Helper/CertificateLocatorHelper.php | 79 ------ .../src/Helper/DigitalPostHelper.php | 8 +- .../src/Helper/KeyCertificateLocator.php | 59 +++++ .../src/Helper/MemoryCertificateLocator.php | 49 ++++ .../src/Helper/Settings.php | 146 +++++++---- package.json | 13 - scripts/code-analysis | 18 +- 20 files changed, 418 insertions(+), 377 deletions(-) create mode 100644 .markdownlint.jsonc delete mode 100644 .markdownlintrc delete mode 100644 modules/os2forms_digital_post/src/Exception/InvalidSettingException.php delete mode 100644 modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php create mode 100644 modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php create mode 100644 modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php delete mode 100644 package.json diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f50b23dc..c78e9623 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -50,6 +50,9 @@ jobs: composer validate --strict composer.json # Check that dependencies resolve. composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + - name: Check that composer file is normalized + run: | + composer normalize --dry-run php-coding-standards: name: PHP coding standards @@ -113,27 +116,13 @@ jobs: run: | ./scripts/code-analysis - markdownlint: + coding-standards-markdown: + name: Markdown coding standards 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 + uses: actions/checkout@master + + - name: Coding standards + run: | + docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' diff --git a/.gitignore b/.gitignore index 1cc8643c..1a6c2523 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,3 @@ composer.lock vendor -node_modules/ -yarn.lock diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 00000000..a28c5809 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,13 @@ +{ + "default": true, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md + "line-length": { + "line_length": 120, + "code_blocks": false, + "tables": false + }, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md + "no-duplicate-heading": { + "siblings_only": true + } +} diff --git a/.markdownlintrc b/.markdownlintrc deleted file mode 100644 index 75637156..00000000 --- a/.markdownlintrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - // @see https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.jsonc - // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md - "MD013": { - // Exclude code blocks - "code_blocks": false - }, - - // Prevent complaining on duplicated headings in CHANGELOG.md - // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md - "MD024": { - "siblings_only": true - } -} - -// Local Variables: -// mode: json -// End: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8146cb76..4f03e6d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] +- [#101](https://github.com/OS2Forms/os2forms/pull/101) + Added support for os2web_key + ## [3.21.0] 2024-12-17 - Updated `os2web_audit`. diff --git a/README.md b/README.md index 12e4ad05..90245a21 100644 --- a/README.md +++ b/README.md @@ -122,29 +122,27 @@ run the checks locally. ```sh docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer install -docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer coding-standards-check - # Fix (some) coding standards issues. docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer coding-standards-apply +docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer coding-standards-check ``` ### Markdown ```sh -docker run --rm --volume ${PWD}:/app --workdir /app node:20 yarn install -docker run --rm --volume ${PWD}:/app --workdir /app node:20 yarn coding-standards-check/markdownlint - -# Fix (some) coding standards issues. -docker run --rm --volume ${PWD}:/app --workdir /app node:20 yarn coding-standards-apply/markdownlint +docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' --fix +docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' ``` ## Code analysis We use [PHPStan](https://phpstan.org/) for static code analysis. -Running statis code analysis on a standalone Drupal module is a bit tricky, so -we use a helper script to run the analysis: +Running statis code analysis on a standalone Drupal module is a bit tricky, so we use a helper script to run the +analysis: -```sh -./scripts/code-analysis +```shell +docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm ./scripts/code-analysis ``` + +**Note**: Currently the code analysis is only run on the `os2forms_digital_post` sub-module (cf. [`phpstan.neon`](./phpstan.neon)). diff --git a/composer.json b/composer.json index 401909b2..1bcbbd1f 100644 --- a/composer.json +++ b/composer.json @@ -1,20 +1,8 @@ { "name": "os2forms/os2forms", - "type": "drupal-module", "description": "Drupal 8 OS2Form module provides advanced webform functionality 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" - } - }, + "type": "drupal-module", "require": { "php": "^8.1", "ext-dom": "*", @@ -43,7 +31,7 @@ "drupal/mailsystem": "^4.1", "drupal/masquerade": "^2.0@RC", "drupal/pathauto": "^1.5", - "drupal/permissions_by_term": "^3.1 || ^2.25", + "drupal/permissions_by_term": "^2.25 || ^3.1", "drupal/queue_mail": "^1.4", "drupal/r4032login": "^2.1", "drupal/redirect": "^1.4", @@ -70,6 +58,7 @@ "itk-dev/serviceplatformen": "^1.5", "os2web/os2web_audit": "^0.1.6", "os2web/os2web_datalookup": "^2.0", + "os2web/os2web_key": "dev-os2web_key", "os2web/os2web_nemlogin": "^1.0", "php-http/guzzle7-adapter": "^1.0", "phpoffice/phpword": "^0.18.2", @@ -83,15 +72,44 @@ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", "drupal/coder": "^8.3", "drupal/maillog": "^1.0", + "ergebnis/composer-normalize": "^2.42", "mglaman/phpstan-drupal": "^1.1", "phpstan/extension-installer": "^1.3", "phpstan/phpstan-deprecation-rules": "^1.1", "phpunit/phpunit": "^9.5", "wsdltophp/packagegenerator": "^4.0" }, - "extra" : { + "repositories": { + "os2web/os2web_key": { + "type": "vcs", + "url": "https://github.com/itk-dev/os2web_key" + }, + "drupal": { + "type": "composer", + "url": "https://packages.drupal.org/8" + }, + "assets": { + "type": "composer", + "url": "https://asset-packagist.org" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "allow-plugins": { + "cweagans/composer-patches": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "ergebnis/composer-normalize": true, + "phpstan/extension-installer": true, + "simplesamlphp/composer-module-installer": true, + "vaimo/composer-patches": true, + "zaporylie/composer-drupal-optimizations": true + }, + "sort-packages": true + }, + "extra": { "composer-exit-on-patch-failure": false, - "enable-patching" : true, + "enable-patching": true, "patches": { "drupal/entity_print": { "2733781 - Add Export to Word Support": "https://www.drupal.org/files/issues/2019-11-22/2733781-47.patch" @@ -123,23 +141,23 @@ } }, "scripts": { - "code-analysis/phpstan": [ - "phpstan analyse" - ], "code-analysis": [ "@code-analysis/phpstan" ], - "coding-standards-check/phpcs": [ - "phpcs --standard=phpcs.xml.dist" + "code-analysis/phpstan": [ + "phpstan analyse" ], - "coding-standards-check": [ - "@coding-standards-check/phpcs" + "coding-standards-apply": [ + "@coding-standards-apply/phpcs" ], "coding-standards-apply/phpcs": [ "phpcbf --standard=phpcs.xml.dist" ], - "coding-standards-apply": [ - "@coding-standards-apply/phpcs" + "coding-standards-check": [ + "@coding-standards-check/phpcs" + ], + "coding-standards-check/phpcs": [ + "phpcs --standard=phpcs.xml.dist" ] }, "config": { diff --git a/modules/os2forms_digital_post/README.md b/modules/os2forms_digital_post/README.md index a487b13e..c36e9f48 100644 --- a/modules/os2forms_digital_post/README.md +++ b/modules/os2forms_digital_post/README.md @@ -31,6 +31,10 @@ examples](modules/os2forms_digital_post_examples/README.md). Go to `/admin/os2forms_digital_post/settings` to set up global settings for digital post. +### Key + +We use [os2web_key](https://github.com/OS2web/os2web_key) to provide the certificate for sending digital post. + ### Queue The actual sending of digital post is handled by jobs in an [Advanced diff --git a/modules/os2forms_digital_post/os2forms_digital_post.info.yml b/modules/os2forms_digital_post/os2forms_digital_post.info.yml index 71a17688..0e1408cc 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.info.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.info.yml @@ -7,6 +7,7 @@ dependencies: - 'beskedfordeler:beskedfordeler' - 'drupal:advancedqueue' - 'os2web_datalookup:os2web_datalookup' + - 'os2web_key:os2web_key' - 'webform:webform' - 'webform:webform_submission_log' - 'os2web:os2web_audit' diff --git a/modules/os2forms_digital_post/os2forms_digital_post.install b/modules/os2forms_digital_post/os2forms_digital_post.install index 760743cb..80b756ac 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.install +++ b/modules/os2forms_digital_post/os2forms_digital_post.install @@ -17,3 +17,12 @@ use Drupal\os2forms_digital_post\Helper\BeskedfordelerHelper; function os2forms_digital_post_schema() { return Drupal::service(BeskedfordelerHelper::class)->schema(); } + +/** + * Implements hook_update_N(). + */ +function os2forms_digital_post_update_9001() { + \Drupal::service('module_installer')->install([ + 'os2web_key', + ], TRUE); +} diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index c13fb96f..66bc3132 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -9,11 +9,13 @@ services: Drupal\os2forms_digital_post\Helper\Settings: arguments: - - "@keyvalue" + - "@config.factory" + - "@key.repository" Drupal\os2forms_digital_post\Helper\CertificateLocatorHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" + - "@key.repository" Drupal\os2forms_digital_post\Helper\MeMoHelper: arguments: @@ -30,7 +32,7 @@ services: Drupal\os2forms_digital_post\Helper\DigitalPostHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@Drupal\\os2forms_digital_post\\Helper\\CertificateLocatorHelper" + - "@Drupal\\os2web_key\\CertificateHelper" - "@plugin.manager.os2web_datalookup" - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" diff --git a/modules/os2forms_digital_post/src/Exception/InvalidSettingException.php b/modules/os2forms_digital_post/src/Exception/InvalidSettingException.php deleted file mode 100644 index c3d34af6..00000000 --- a/modules/os2forms_digital_post/src/Exception/InvalidSettingException.php +++ /dev/null @@ -1,10 +0,0 @@ -queueStorage = $entityTypeManager->getStorage('advancedqueue_queue'); } @@ -44,12 +46,23 @@ public function __construct( */ public static function create(ContainerInterface $container) { return new static( + $container->get('config.factory'), + $container->get('entity_type.manager'), $container->get(Settings::class), - $container->get(CertificateLocatorHelper::class), - $container->get('entity_type.manager') ); } + /** + * {@inheritdoc} + * + * @phpstan-return array + */ + protected function getEditableConfigNames() { + return [ + Settings::CONFIG_NAME, + ]; + } + /** * {@inheritdoc} */ @@ -63,15 +76,26 @@ public function getFormId() { * @phpstan-param array $form * @phpstan-return array */ - public function buildForm(array $form, FormStateInterface $form_state) { - $form['test_mode'] = [ + public function buildForm(array $form, FormStateInterface $form_state): array { + $form = parent::buildForm($form, $form_state); + + $form['message'] = [ + '#theme' => 'status_messages', + '#message_list' => [ + 'status' => [ + $this->t('Use drush os2forms-digital-post:test:send to test sending digital post.'), + ], + ], + ]; + + $form[Settings::TEST_MODE] = [ '#type' => 'checkbox', '#title' => $this->t('Test mode'), - '#default_value' => $this->settings->getTestMode(), + '#default_value' => $this->settings->getEditableValue(Settings::TEST_MODE), + '#description' => $this->createDescription(Settings::TEST_MODE), ]; - $sender = $this->settings->getSender(); - $form['sender'] = [ + $form[Settings::SENDER] = [ '#type' => 'fieldset', '#title' => $this->t('Sender'), '#tree' => TRUE, @@ -82,126 +106,74 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#options' => [ 'CVR' => $this->t('CVR'), ], - '#default_value' => $sender[Settings::SENDER_IDENTIFIER_TYPE] ?? 'CVR', + '#default_value' => $this->settings->getEditableValue([Settings::SENDER, Settings::SENDER_IDENTIFIER_TYPE]) ?? 'CVR', '#required' => TRUE, + '#description' => $this->createDescription([Settings::SENDER, Settings::SENDER_IDENTIFIER_TYPE]), ], Settings::SENDER_IDENTIFIER => [ '#type' => 'textfield', '#title' => $this->t('Identifier'), - '#default_value' => $sender[Settings::SENDER_IDENTIFIER] ?? NULL, + '#default_value' => $this->settings->getEditableValue([Settings::SENDER, Settings::SENDER_IDENTIFIER]), '#required' => TRUE, + '#description' => $this->createDescription([Settings::SENDER, Settings::SENDER_IDENTIFIER]), ], Settings::FORSENDELSES_TYPE_IDENTIFIKATOR => [ '#type' => 'textfield', '#title' => $this->t('Forsendelsestypeidentifikator'), - '#default_value' => $sender[Settings::FORSENDELSES_TYPE_IDENTIFIKATOR] ?? NULL, + '#default_value' => $this->settings->getEditableValue([ + Settings::SENDER, Settings::FORSENDELSES_TYPE_IDENTIFIKATOR, + ]), '#required' => TRUE, + '#description' => $this->createDescription([Settings::SENDER, Settings::FORSENDELSES_TYPE_IDENTIFIKATOR]), ], ]; - $certificate = $this->settings->getCertificate(); - $form['certificate'] = [ + $form[Settings::CERTIFICATE] = [ '#type' => 'fieldset', '#title' => $this->t('Certificate'), '#tree' => TRUE, - 'locator_type' => [ - '#type' => 'select', - '#title' => $this->t('Certificate locator type'), - '#options' => [ - 'azure_key_vault' => $this->t('Azure key vault'), - 'file_system' => $this->t('File system'), - ], - '#default_value' => $certificate['locator_type'] ?? NULL, - ], - ]; - - $form['certificate'][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT] = [ - '#type' => 'fieldset', - '#title' => $this->t('Azure key vault'), - '#states' => [ - 'visible' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT]], - ], - ]; - - $settings = [ - 'tenant_id' => ['title' => $this->t('Tenant id')], - 'application_id' => ['title' => $this->t('Application id')], - 'client_secret' => ['title' => $this->t('Client secret')], - 'name' => ['title' => $this->t('Name')], - 'secret' => ['title' => $this->t('Secret')], - 'version' => ['title' => $this->t('Version')], - ]; - - foreach ($settings as $key => $info) { - $form['certificate'][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] = [ - '#type' => 'textfield', - '#title' => $info['title'], - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] ?? NULL, - '#states' => [ - 'required' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT]], - ], - ]; - } - - $form['certificate'][CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM] = [ - '#type' => 'fieldset', - '#title' => $this->t('File system'), - '#states' => [ - 'visible' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]], - ], - - 'path' => [ - '#type' => 'textfield', - '#title' => $this->t('Path'), - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]['path'] ?? NULL, - '#states' => [ - 'required' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]], + Settings::KEY => [ + '#type' => 'key_select', + '#key_filters' => [ + 'type' => 'os2web_key_certificate', ], + '#key_description' => FALSE, + '#title' => $this->t('Key'), + '#default_value' => $this->settings->getEditableValue([Settings::CERTIFICATE, Settings::KEY]), + '#required' => TRUE, + '#description' => $this->createDescription([Settings::CERTIFICATE, Settings::KEY]), ], ]; - $form['certificate']['passphrase'] = [ - '#type' => 'textfield', - '#title' => $this->t('Passphrase'), - '#default_value' => $certificate['passphrase'] ?? NULL, - ]; - - $processing = $this->settings->getProcessing(); - $form['processing'] = [ + $form[Settings::PROCESSING] = [ '#type' => 'fieldset', '#title' => $this->t('Processing'), '#tree' => TRUE, ]; - $defaultValue = $processing['queue'] ?? 'os2forms_digital_post'; - $form['processing']['queue'] = [ + $queue = $this->settings->getEditableValue([Settings::PROCESSING, Settings::QUEUE]); + $form[Settings::PROCESSING][Settings::QUEUE] = [ '#type' => 'select', '#title' => $this->t('Queue'), '#options' => array_map( static fn(EntityInterface $queue) => $queue->label(), $this->queueStorage->loadMultiple() ), - '#default_value' => $defaultValue, - '#description' => $this->t("Queue for digital post jobs. The queue must be run via Drupal's cron or via drush advancedqueue:queue:process @queue(in a cron job).", [ - '@queue' => $defaultValue, - ':queue_url' => '/admin/config/system/queues/jobs/' . urlencode($defaultValue), - ]), - ]; - - $form['actions']['#type'] = 'actions'; - - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Save settings'), - ]; - - $form['actions']['testCertificate'] = [ - '#type' => 'submit', - '#name' => 'testCertificate', - '#value' => $this->t('Test certificate'), + '#required' => TRUE, + '#default_value' => $queue, + '#description' => $this->createDescription([Settings::PROCESSING, Settings::QUEUE], + $queue + ? $this->t("Queue for digital post jobs. The queue must be run via Drupal's cron or via drush advancedqueue:queue:process @queue (in a cron job).", [ + '@queue' => $queue, + ':queue_url' => Url::fromRoute('view.advancedqueue_jobs.page_1', [ + 'arg_0' => $queue, + ])->toString(TRUE)->getGeneratedUrl(), + ]) + : $this->t("Queue for digital post jobs. The queue must be processed via Drupal's cron or drush advancedqueue:queue:process (in a cron job)."), + ), ]; return $form; @@ -212,59 +184,41 @@ public function buildForm(array $form, FormStateInterface $form_state) { * * @phpstan-param array $form */ - public function validateForm(array &$form, FormStateInterface $formState): void { - $triggeringElement = $formState->getTriggeringElement(); - if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { - return; + public function submitForm(array &$form, FormStateInterface $form_state): void { + $config = $this->config(Settings::CONFIG_NAME); + foreach ([ + Settings::TEST_MODE, + Settings::SENDER, + Settings::CERTIFICATE, + Settings::PROCESSING, + ] as $key) { + $config->set($key, $form_state->getValue($key)); } + $config->save(); - $values = $formState->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])); - } - } + parent::submitForm($form, $form_state); } /** - * {@inheritdoc} + * Create form field description with information on any runtime override. * - * @phpstan-param array $form + * @param string|array $key + * The key. + * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $description + * The actual field description. + * + * @return string + * The full description. */ - public function submitForm(array &$form, FormStateInterface $formState): void { - $triggeringElement = $formState->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'); - $this->settings->setSettings($settings); - $this->messenger()->addStatus($this->t('Settings saved')); - } - catch (OptionsResolverException $exception) { - $this->messenger()->addError($this->t('Settings not saved (@message)', ['@message' => $exception->getMessage()])); + private function createDescription(string|array $key, ?TranslatableMarkup $description = NULL): string { + if ($value = $this->settings->getOverride($key)) { + if (!empty($description)) { + $description .= '
'; + } + $description .= $this->t('Note: overridden on runtime with the value @value.', ['@value' => var_export($value['runtime'], TRUE)]); } - } - /** - * Test certificate. - */ - private function testCertificate(): void { - try { - $certificateLocator = $this->certificateLocatorHelper->getCertificateLocator(); - $certificateLocator->getCertificates(); - $this->messenger()->addStatus($this->t('Certificate succesfully tested')); - } - catch (\Throwable $throwable) { - $message = $this->t('Error testing certificate: %message', ['%message' => $throwable->getMessage()]); - $this->messenger()->addError($message); - } + return (string) $description; } } diff --git a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php deleted file mode 100644 index 10e6ac57..00000000 --- a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php +++ /dev/null @@ -1,79 +0,0 @@ -settings->getCertificate(); - - $locatorType = $certificateSettings['locator_type']; - $options = $certificateSettings[$locatorType]; - $options += [ - 'passphrase' => $certificateSettings['passphrase'] ?: '', - ]; - - if (self::LOCATOR_TYPE_AZURE_KEY_VAULT === $locatorType) { - $httpClient = new GuzzleAdapter(new Client()); - $requestFactory = new RequestFactory(); - - $vaultToken = new VaultToken($httpClient, $requestFactory); - - $token = $vaultToken->getToken( - $options['tenant_id'], - $options['application_id'], - $options['client_secret'], - ); - - $vault = new VaultSecret( - $httpClient, - $requestFactory, - $options['name'], - $token->getAccessToken() - ); - - return new AzureKeyVaultCertificateLocator( - $vault, - $options['secret'], - $options['version'], - $options['passphrase'], - ); - } - elseif (self::LOCATOR_TYPE_FILE_SYSTEM === $locatorType) { - $certificatepath = realpath($options['path']) ?: NULL; - if (NULL === $certificatepath) { - throw new CertificateLocatorException(sprintf('Invalid certificate path %s', $options['path'])); - } - return new FilesystemCertificateLocator($certificatepath, $options['passphrase']); - } - - throw new CertificateLocatorException(sprintf('Invalid certificate locator type: %s', $locatorType)); - } - -} diff --git a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php index 8681cf35..fc2355af 100644 --- a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php +++ b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php @@ -11,6 +11,7 @@ use Drupal\os2web_datalookup\Plugin\DataLookupManager; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCompanyInterface; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCprInterface; +use Drupal\os2web_key\CertificateHelper; use Drupal\webform\WebformSubmissionInterface; use ItkDev\Serviceplatformen\Service\SF1601\SF1601; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; @@ -29,7 +30,7 @@ final class DigitalPostHelper implements LoggerInterface { */ public function __construct( private readonly Settings $settings, - private readonly CertificateLocatorHelper $certificateLocatorHelper, + private readonly CertificateHelper $certificateHelper, private readonly DataLookupManager $dataLookupManager, private readonly MeMoHelper $meMoHelper, private readonly ForsendelseHelper $forsendelseHelper, @@ -62,7 +63,10 @@ public function sendDigitalPost(string $type, Message $message, ?ForsendelseI $f $options = [ 'test_mode' => (bool) $this->settings->getTestMode(), 'authority_cvr' => $senderSettings[Settings::SENDER_IDENTIFIER], - 'certificate_locator' => $this->certificateLocatorHelper->getCertificateLocator(), + 'certificate_locator' => new KeyCertificateLocator( + $this->settings->getCertificateKey(), + $this->certificateHelper + ), ]; $service = new SF1601($options); $transactionId = Serializer::createUuid(); diff --git a/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php b/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php new file mode 100644 index 00000000..832bc19f --- /dev/null +++ b/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php @@ -0,0 +1,59 @@ + + */ + public function getCertificates(): array { + if (!isset($this->certificates)) { + $this->certificates = $this->certificateHelper->getCertificates($this->key); + } + + return $this->certificates; + } + + /** + * {@inheritdoc} + */ + public function getCertificate(): string { + return $this->key->getKeyValue(); + } + + /** + * {@inheritdoc} + */ + public function getAbsolutePathToCertificate(): string { + throw new CertificateLocatorException(__METHOD__ . ' should not be used.'); + } + +} diff --git a/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php b/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php new file mode 100644 index 00000000..69d791d2 --- /dev/null +++ b/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php @@ -0,0 +1,49 @@ + + */ + public function getCertificates(): array { + $certificates = []; + $this->passphrase = 'P5bISuw?s:u4'; + if (!openssl_pkcs12_read($this->certificate, $certificates, $this->passphrase)) { + throw new CertificateLocatorException(sprintf('Could not read certificate: %s', openssl_error_string() ?: '')); + } + + return $certificates; + } + + /** + * {@inheritdoc} + */ + public function getCertificate(): string { + return $this->certificate; + } + + /** + * {@inheritdoc} + */ + public function getAbsolutePathToCertificate(): string { + throw new CertificateLocatorException(__METHOD__ . ' should not be used.'); + } + +} diff --git a/modules/os2forms_digital_post/src/Helper/Settings.php b/modules/os2forms_digital_post/src/Helper/Settings.php index e64be738..c0d5384d 100644 --- a/modules/os2forms_digital_post/src/Helper/Settings.php +++ b/modules/os2forms_digital_post/src/Helper/Settings.php @@ -2,45 +2,61 @@ namespace Drupal\os2forms_digital_post\Helper; -use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; -use Drupal\Core\KeyValueStore\KeyValueStoreInterface; -use Drupal\os2forms_digital_post\Exception\InvalidSettingException; -use Symfony\Component\OptionsResolver\OptionsResolver; +use Drupal\Core\Config\Config; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; +use Drupal\key\KeyInterface; +use Drupal\key\KeyRepositoryInterface; /** * General settings for os2forms_digital_post. */ final class Settings { + public const CONFIG_NAME = 'os2forms_digital_post.settings'; + + public const TEST_MODE = 'test_mode'; + + public const SENDER = 'sender'; public const SENDER_IDENTIFIER_TYPE = 'sender_identifier_type'; public const SENDER_IDENTIFIER = 'sender_identifier'; public const FORSENDELSES_TYPE_IDENTIFIKATOR = 'forsendelses_type_identifikator'; + public const CERTIFICATE = 'certificate'; + public const KEY = 'key'; + + public const PROCESSING = 'processing'; + public const QUEUE = 'queue'; + /** - * The store. + * The runtime (immutable) config. * - * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface + * @var \Drupal\Core\Config\ImmutableConfig */ - private KeyValueStoreInterface $store; + private ImmutableConfig $runtimeConfig; /** - * The key prefix. + * The (mutable) config. * - * @var string + * @var \Drupal\Core\Config\Config */ - private $collection = 'os2forms_digital_post.'; + private Config $editableConfig; /** - * Constructor. + * The constructor. */ - public function __construct(KeyValueFactoryInterface $keyValueFactory) { - $this->store = $keyValueFactory->get($this->collection); + public function __construct( + ConfigFactoryInterface $configFactory, + private readonly KeyRepositoryInterface $keyRepository, + ) { + $this->runtimeConfig = $configFactory->get(self::CONFIG_NAME); + $this->editableConfig = $configFactory->getEditable(self::CONFIG_NAME); } /** * Get test mode. */ public function getTestMode(): bool { - return (bool) $this->get('test_mode', TRUE); + return (bool) $this->get(self::TEST_MODE, TRUE); } /** @@ -49,18 +65,25 @@ public function getTestMode(): bool { * @phpstan-return array */ public function getSender(): array { - $value = $this->get('sender'); + $value = $this->get(self::SENDER); + return is_array($value) ? $value : []; } + /** + * Get key. + */ + public function getKey(): ?string { + return $this->get([self::CERTIFICATE, self::KEY]); + } + /** * Get certificate. - * - * @phpstan-return array */ - public function getCertificate(): array { - $value = $this->get('certificate'); - return is_array($value) ? $value : []; + public function getCertificateKey(): ?KeyInterface { + return $this->keyRepository->getKey( + $this->getKey(), + ); } /** @@ -69,57 +92,82 @@ public function getCertificate(): array { * @phpstan-return array */ public function getProcessing(): array { - $value = $this->get('processing'); + $value = $this->get(self::PROCESSING); + return is_array($value) ? $value : []; } /** - * Get a setting value. + * Get editable value. * - * @param string $key + * @param string|array $key * The key. - * @param mixed|null $default - * The default value. * * @return mixed - * The setting value. + * The editable value. */ - private function get(string $key, $default = NULL) { - $resolver = $this->getSettingsResolver(); - if (!$resolver->isDefined($key)) { - throw new InvalidSettingException(sprintf('Setting %s is not defined', $key)); + public function getEditableValue(string|array $key): mixed { + if (is_array($key)) { + $key = implode('.', $key); } - - return $this->store->get($key, $default); + return $this->editableConfig->get($key); } /** - * Set settings. + * Get runtime value override if any. * - * @throws \Symfony\Component\OptionsResolver\Exception\ExceptionInterface + * @param string|array $key + * The key. * - * @phpstan-param array $settings + * @return array|null + * - 'runtime': the runtime value + * - 'editable': the editable (raw) value */ - public function setSettings(array $settings): self { - $settings = $this->getSettingsResolver()->resolve($settings); - foreach ($settings as $key => $value) { - $this->store->set($key, $value); + public function getOverride(string|array $key): ?array { + $runtimeValue = $this->getRuntimeValue($key); + $editableValue = $this->getEditableValue($key); + + // Note: We deliberately use "Equal" (==) rather than "Identical" (===) + // to compare values (cf. https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison). + if ($runtimeValue == $editableValue) { + return NULL; } - return $this; + return [ + 'runtime' => $runtimeValue, + 'editable' => $editableValue, + ]; } /** - * Get settings resolver. + * Get a setting value. + * + * @param string|array $key + * The key. + * @param mixed $default + * The default value. + * + * @return mixed + * The setting value. */ - private function getSettingsResolver(): OptionsResolver { - return (new OptionsResolver()) - ->setDefaults([ - 'test_mode' => TRUE, - 'sender' => [], - 'certificate' => [], - 'processing' => [], - ]); + private function get(string|array $key, mixed $default = NULL) { + return $this->getRuntimeValue($key) ?? $default; + } + + /** + * Get runtime value with any overrides applied. + * + * @param string|array $key + * The key. + * + * @return mixed + * The runtime value. + */ + public function getRuntimeValue(string|array $key): mixed { + if (is_array($key)) { + $key = implode('.', $key); + } + return $this->runtimeConfig->get($key); } } diff --git a/package.json b/package.json deleted file mode 100644 index 52fcd34c..00000000 --- a/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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/scripts/code-analysis b/scripts/code-analysis index 9fec0f46..ace9e282 100755 --- a/scripts/code-analysis +++ b/scripts/code-analysis @@ -16,9 +16,21 @@ if [ ! -f "$drupal_dir/composer.json" ]; then composer --no-interaction create-project drupal/recommended-project:^10 "$drupal_dir" fi # Copy our code into the modules folder -mkdir -p "$drupal_dir/$module_path" + +# Clean up +rm -fr "${drupal_dir:?}/$module_path" + # https://stackoverflow.com/a/15373763 -rsync --archive --compress . --filter=':- .gitignore' --exclude "$drupal_dir" --exclude .git "$drupal_dir/$module_path" +# rsync --archive --compress . --filter=':- .gitignore' --exclude "$drupal_dir" --exclude .git "$drupal_dir/$module_path" + +# The rsync command in not available in itkdev/php8.1-fpm + +git config --global --add safe.directory /app +# Copy module files into module path +for f in $(git ls-files); do + mkdir -p "$drupal_dir/$module_path/$(dirname "$f")" + cp "$f" "$drupal_dir/$module_path/$f" +done drupal_composer config minimum-stability dev @@ -37,4 +49,4 @@ drupal_composer config extra.merge-plugin.include "$module_path/composer.json" drupal_composer require --dev symfony/phpunit-bridge # Run PHPStan -(cd "$drupal_dir" && vendor/bin/phpstan --configuration="$module_path/phpstan.neon") +(cd "$drupal_dir/$module_path" && ../../../../vendor/bin/phpstan) From b07e53658b65ad3ddebc22ebb30e626cc30becea Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 7 May 2024 14:26:39 +0200 Subject: [PATCH 08/88] Cleaned up Markdown --- modules/os2forms_attachment/README.md | 14 ++++++++------ modules/os2forms_autocomplete/README.md | 9 +++++---- modules/os2forms_dawa/README.md | 7 ++++--- modules/os2forms_forloeb/CHANGELOG.md | 9 +++++++-- modules/os2forms_forloeb/README.md | 16 ++++++++-------- modules/os2forms_nemid/README.md | 5 ++--- modules/os2forms_permissions_by_term/README.md | 7 ++++++- modules/os2forms_sbsys/README.md | 1 + modules/os2forms_webform_maps/README.md | 3 +++ 9 files changed, 44 insertions(+), 27 deletions(-) diff --git a/modules/os2forms_attachment/README.md b/modules/os2forms_attachment/README.md index f732ad2f..5b1c7501 100644 --- a/modules/os2forms_attachment/README.md +++ b/modules/os2forms_attachment/README.md @@ -1,20 +1,22 @@ # OS2Forms Attachment Drupal module -# Module purpose +## Module purpose The aim of this module is to provide an OS2forms attachment element for adding PDF/HTML attachment. It also supports creation of reusable headers/footers components which are used when rendering the attachments. -# How does it work +## How does it work -To add custom headers/footer ```admin/structure/webform/config/os2forms_attachment_component``` +To add custom headers/footer `admin/structure/webform/config/os2forms_attachment_component` -To specify headers/footers that will override the default ones on a global level (**Third party settings** -> **Entity print** section): ```admin/structure/webform/config``` +To specify headers/footers that will override the default ones on a global level (**Third party settings** -> **Entity +print** section): `admin/structure/webform/config` -To specify headers/footers that will override the default ones on a form level (**Third party settings** -> **Entity print** section): ```/admin/structure/webform/manage/[webform]/settings``` +To specify headers/footers that will override the default ones on a form level (**Third party settings** -> **Entity +print** section): ```/admin/structure/webform/manage/[webform]/settings``` -# Overwriting templates +## Overwriting templates With some setups it might be necessary to overwrite templates in order to access stylesheets or images. diff --git a/modules/os2forms_autocomplete/README.md b/modules/os2forms_autocomplete/README.md index d5902385..2294d45e 100644 --- a/modules/os2forms_autocomplete/README.md +++ b/modules/os2forms_autocomplete/README.md @@ -1,15 +1,16 @@ # OS2Forms Autocomplete Drupal module -# Module purpose +## Module purpose -The aim of this module is to provide a genetic OS2Forms Autocomplete element which can return options from an external webservice. +The aim of this module is to provide a genetic OS2Forms Autocomplete element which can return options from an external +webservice. -# How does it work +## How does it work Module exposes OS2Forms Autocomplete component that are available in the webform build process. Build page: -``` +```url admin/structure/webform/manage/[webform] ``` diff --git a/modules/os2forms_dawa/README.md b/modules/os2forms_dawa/README.md index 53876ba3..ae242ade 100644 --- a/modules/os2forms_dawa/README.md +++ b/modules/os2forms_dawa/README.md @@ -1,10 +1,11 @@ # OS2Forms DAWA Drupal module -# Module purpose +## Module purpose -The aim of this module is to provide integration with Danish Addresses Web API (DAWA https://dawa.aws.dk) and provider address autocomplete fields. +The aim of this module is to provide integration with Danish Addresses Web API (DAWA ) and provider +address autocomplete fields. -# How does it work +## How does it work Module exposes couple of new Autocomplete components that are available in the webform build process. diff --git a/modules/os2forms_forloeb/CHANGELOG.md b/modules/os2forms_forloeb/CHANGELOG.md index 3338d105..6d569821 100644 --- a/modules/os2forms_forloeb/CHANGELOG.md +++ b/modules/os2forms_forloeb/CHANGELOG.md @@ -1,4 +1,5 @@ # OS2Forms Forløb Change Log + All notable changes to this project should be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) @@ -17,9 +18,11 @@ before starting to add changes. ## 2.5.2 - 27.03.2023 ### Updated + - Bumped drupal/ultimate_cron version fixing [Deprecated function: Implicit conversion from float-string](https://www.drupal.org/project/ultimate_cron/issues/3256142). ## 2.5.1 - 10.03.2023 + - Added github action for checking changelog changes when creating pull requests - Added os2forms/os2forms dependency - Changed composer patching configuration @@ -30,18 +33,20 @@ before starting to add changes. ## 2.5.0 - 11.10.2022 ### Added + - retry task controller action - Added support for inheriting values without creating a submission ## 2.4.0 ### Added + - Github CI action for checking Drupal Coding standards with PHP Code Sniffer - Fixed coding standards issues - ## Example of change log record -``` + +```markdown ## [x.x.x] Release name ### Added - Description on added functionality. diff --git a/modules/os2forms_forloeb/README.md b/modules/os2forms_forloeb/README.md index 7cf06186..11d1b07b 100644 --- a/modules/os2forms_forloeb/README.md +++ b/modules/os2forms_forloeb/README.md @@ -4,11 +4,13 @@ Adds a Maestro workflow engine and advanced workflow functionality to OS2forms. ## Installing OS2forms 2.1 med Forløb -This module requires the codebase from the [OS2forms core project](https://github.com/OS2Forms/os2forms8) installed per the documentation and by selecting the os2forms_forloeb_profile at installation. After succesful installation you should have the OS2forms med Forløb Module available for install via gui. +This module requires the codebase from the [OS2forms core project](https://github.com/OS2Forms/os2forms8) installed per +the documentation and by selecting the os2forms_forloeb_profile at installation. After succesful installation you should +have the OS2forms med Forløb Module available for install via gui. You can also install the module by using Drush: -``` +```shell ./vendor/bin/drush pm:enable os2forms_forloeb ``` @@ -32,20 +34,18 @@ Settings for OS2Forms forløb are defined on `/admin/config/system/os2forms_forl #### Known anonymous roles -In order to make the notifications work, Maestro workflow tasks must be assigned -to a *known anonymous role* and these roles are defined under *Known anonymous -roles*. +In order to make the notifications work, Maestro workflow tasks must be assigned to a *known anonymous role* and these +roles are defined under *Known anonymous roles*. #### Processing -A notification is not sent to a user immediately, but added to a queue which -must be processed asynchronously. Specify the queue handling notification jobs. +A notification is not sent to a user immediately, but added to a queue which must be processed asynchronously. Specify +the queue handling notification jobs. #### Templates Define templates for emails and digital post (PDF). - To reference assets, e.g. stylesheet or images, in your templates, you can use the `base_url` Twig variable to get the base URL: diff --git a/modules/os2forms_nemid/README.md b/modules/os2forms_nemid/README.md index 98ec5532..ec7c7932 100644 --- a/modules/os2forms_nemid/README.md +++ b/modules/os2forms_nemid/README.md @@ -1,10 +1,10 @@ # OS2Forms Nemid Drupal module -# Module purpose +## Module purpose The aim of this module is to provide custom NemId field and integration with OS2Web Nemlogin module. -# How does it work +## How does it work Module exposes dozen of new NemID components that are available in the webform build process. @@ -17,4 +17,3 @@ Besides this module adds a special settings to the Third Party Webform settings: - Hide form if under address protection Settings: admin/structure/webform/manage/[webform]/settings - diff --git a/modules/os2forms_permissions_by_term/README.md b/modules/os2forms_permissions_by_term/README.md index 2c572931..33ac9367 100644 --- a/modules/os2forms_permissions_by_term/README.md +++ b/modules/os2forms_permissions_by_term/README.md @@ -1,10 +1,13 @@ # OS2Forms permission by term module + This module implements permission by term access restrictions on several lists and entity displays related to webform and maestro. ## Setup configuration + Add to your settings.php or local.settings.php -``` + +```php $config['permissions_by_term.settings'] = [ 'permissions_mode' => FALSE, 'require_all_terms_granted' => FALSE, @@ -12,6 +15,7 @@ $config['permissions_by_term.settings'] = [ 'target_bundles' => ['user_affiliation'] ] ``` + Alternative change your site configuration on admin/permissions-by-term/settings to match the above. !note This is the recommended configuration of the permissions_by_term module. Using different values for @@ -19,6 +23,7 @@ Alternative change your site configuration on admin/permissions-by-term/settings be thoroughly tested. ## Usage + - The user affiliation taxonomy is added to webform config form and Maestro workflow forms. - The Permissions by Term module adds a form element to the user form. - When a user visits an entity of the above mentioned this module checks for match between the entity and the users diff --git a/modules/os2forms_sbsys/README.md b/modules/os2forms_sbsys/README.md index b554eebc..8e1129f0 100644 --- a/modules/os2forms_sbsys/README.md +++ b/modules/os2forms_sbsys/README.md @@ -1,6 +1,7 @@ # OS2forms SBSYS integration Drupal module ## Module purpose + The aim of this module is to provide integration with SBSYS ESDH provider. ## How does it work diff --git a/modules/os2forms_webform_maps/README.md b/modules/os2forms_webform_maps/README.md index e6d08748..c36b901e 100644 --- a/modules/os2forms_webform_maps/README.md +++ b/modules/os2forms_webform_maps/README.md @@ -1,13 +1,16 @@ # OS2Forms Webform Maps module for Drupal 9 ## Module description + Provides integration with Leaflet maps and provides map element for webform. ## How does it work + The module provides a new element type for webform. The element type is called "OS2Forms Kort". The element type is based on the Leaflet library. The element type provides a map with a marker that can be moved around on the map. The element type also provides ways of changing layers on the map. The data can be exported to PDF. ## Installation + The module can be installed using the standard Drupal installation procedure. From 0f6900dcf3385ef071d4746dc8788727f1b54a33 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 7 May 2024 23:30:35 +0200 Subject: [PATCH 09/88] Cleaned up --- .../os2forms_digital_post.install | 2 +- .../os2forms_digital_post.services.yml | 2 +- .../Commands/DigitalPostTestCommands.php | 4 +- .../src/Form/SettingsForm.php | 6 ++- .../src/Helper/DigitalPostHelper.php | 6 +-- .../src/Helper/KeyCertificateLocator.php | 10 ++-- .../src/Helper/MemoryCertificateLocator.php | 49 ------------------- 7 files changed, 17 insertions(+), 62 deletions(-) delete mode 100644 modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php diff --git a/modules/os2forms_digital_post/os2forms_digital_post.install b/modules/os2forms_digital_post/os2forms_digital_post.install index 80b756ac..f8140579 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.install +++ b/modules/os2forms_digital_post/os2forms_digital_post.install @@ -21,7 +21,7 @@ function os2forms_digital_post_schema() { /** * Implements hook_update_N(). */ -function os2forms_digital_post_update_9001() { +function os2forms_digital_post_update_9001(): void { \Drupal::service('module_installer')->install([ 'os2web_key', ], TRUE); diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 66bc3132..9b096fad 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -32,7 +32,7 @@ services: Drupal\os2forms_digital_post\Helper\DigitalPostHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@Drupal\\os2web_key\\CertificateHelper" + - "@Drupal\\os2web_key\\KeyHelper" - "@plugin.manager.os2web_datalookup" - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" diff --git a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php index 248452a3..8d7d17c0 100644 --- a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php +++ b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php @@ -145,7 +145,9 @@ private function dumpDigitalPostSettings(SymfonyStyle $io): void { Yaml::encode([ 'testMode' => $this->digitalPostSettings->getTestMode(), 'sender' => $this->digitalPostSettings->getSender(), - 'certificate' => $this->digitalPostSettings->getCertificate(), + 'certificate' => [ + 'key' => $this->digitalPostSettings->getKey(), + ], 'processing' => $this->digitalPostSettings->getProcessing(), ]), '', diff --git a/modules/os2forms_digital_post/src/Form/SettingsForm.php b/modules/os2forms_digital_post/src/Form/SettingsForm.php index 6b2e062c..b91e2ed5 100644 --- a/modules/os2forms_digital_post/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_post/src/Form/SettingsForm.php @@ -55,7 +55,7 @@ public static function create(ContainerInterface $container) { /** * {@inheritdoc} * - * @phpstan-return array + * @phpstan-return string[] */ protected function getEditableConfigNames() { return [ @@ -202,13 +202,15 @@ public function submitForm(array &$form, FormStateInterface $form_state): void { /** * Create form field description with information on any runtime override. * - * @param string|array $key + * @param string|array $key * The key. * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $description * The actual field description. * * @return string * The full description. + * + * @phpstan-param string|string[] $key */ private function createDescription(string|array $key, ?TranslatableMarkup $description = NULL): string { if ($value = $this->settings->getOverride($key)) { diff --git a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php index fc2355af..80825dff 100644 --- a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php +++ b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php @@ -11,7 +11,7 @@ use Drupal\os2web_datalookup\Plugin\DataLookupManager; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCompanyInterface; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCprInterface; -use Drupal\os2web_key\CertificateHelper; +use Drupal\os2web_key\KeyHelper; use Drupal\webform\WebformSubmissionInterface; use ItkDev\Serviceplatformen\Service\SF1601\SF1601; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; @@ -30,7 +30,7 @@ final class DigitalPostHelper implements LoggerInterface { */ public function __construct( private readonly Settings $settings, - private readonly CertificateHelper $certificateHelper, + private readonly KeyHelper $keyHelper, private readonly DataLookupManager $dataLookupManager, private readonly MeMoHelper $meMoHelper, private readonly ForsendelseHelper $forsendelseHelper, @@ -65,7 +65,7 @@ public function sendDigitalPost(string $type, Message $message, ?ForsendelseI $f 'authority_cvr' => $senderSettings[Settings::SENDER_IDENTIFIER], 'certificate_locator' => new KeyCertificateLocator( $this->settings->getCertificateKey(), - $this->certificateHelper + $this->keyHelper ), ]; $service = new SF1601($options); diff --git a/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php b/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php index 832bc19f..8d8f0a41 100644 --- a/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php +++ b/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php @@ -3,7 +3,7 @@ namespace Drupal\os2forms_digital_post\Helper; use Drupal\key\KeyInterface; -use Drupal\os2web_key\CertificateHelper; +use Drupal\os2web_key\KeyHelper; use ItkDev\Serviceplatformen\Certificate\AbstractCertificateLocator; use ItkDev\Serviceplatformen\Certificate\Exception\CertificateLocatorException; @@ -15,16 +15,16 @@ class KeyCertificateLocator extends AbstractCertificateLocator { /** * The parsed certificates. * - * @var array + * @var array */ - private readonly array $certificates; + private array $certificates; /** * Constructor. */ public function __construct( private readonly KeyInterface $key, - private readonly CertificateHelper $certificateHelper, + private readonly KeyHelper $keyHelper, ) { parent::__construct(); } @@ -36,7 +36,7 @@ public function __construct( */ public function getCertificates(): array { if (!isset($this->certificates)) { - $this->certificates = $this->certificateHelper->getCertificates($this->key); + $this->certificates = $this->keyHelper->getCertificates($this->key); } return $this->certificates; diff --git a/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php b/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php deleted file mode 100644 index 69d791d2..00000000 --- a/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php +++ /dev/null @@ -1,49 +0,0 @@ - - */ - public function getCertificates(): array { - $certificates = []; - $this->passphrase = 'P5bISuw?s:u4'; - if (!openssl_pkcs12_read($this->certificate, $certificates, $this->passphrase)) { - throw new CertificateLocatorException(sprintf('Could not read certificate: %s', openssl_error_string() ?: '')); - } - - return $certificates; - } - - /** - * {@inheritdoc} - */ - public function getCertificate(): string { - return $this->certificate; - } - - /** - * {@inheritdoc} - */ - public function getAbsolutePathToCertificate(): string { - throw new CertificateLocatorException(__METHOD__ . ' should not be used.'); - } - -} From ae2c683933b6bc5bbeacc4a01982d6a4e650c974 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 13 May 2024 09:11:55 +0200 Subject: [PATCH 10/88] Required os2web_key 1.0 --- composer.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 1bcbbd1f..083b9a89 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "itk-dev/serviceplatformen": "^1.5", "os2web/os2web_audit": "^0.1.6", "os2web/os2web_datalookup": "^2.0", - "os2web/os2web_key": "dev-os2web_key", + "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "php-http/guzzle7-adapter": "^1.0", "phpoffice/phpword": "^0.18.2", @@ -80,10 +80,6 @@ "wsdltophp/packagegenerator": "^4.0" }, "repositories": { - "os2web/os2web_key": { - "type": "vcs", - "url": "https://github.com/itk-dev/os2web_key" - }, "drupal": { "type": "composer", "url": "https://packages.drupal.org/8" From 801cae9a83b769211c9c4b3725b2ef3af4f7f3fd Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 13 May 2024 11:27:36 +0200 Subject: [PATCH 11/88] Updated os2web_datalookup --- composer.json | 6 +++++- modules/os2forms_digital_post/README.md | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 083b9a89..ec6906b0 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ "itk-dev/beskedfordeler-drupal": "^1.0", "itk-dev/serviceplatformen": "^1.5", "os2web/os2web_audit": "^0.1.6", - "os2web/os2web_datalookup": "^2.0", + "os2web/os2web_datalookup": "dev-feature/os2web_key as 1.12.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "php-http/guzzle7-adapter": "^1.0", @@ -80,6 +80,10 @@ "wsdltophp/packagegenerator": "^4.0" }, "repositories": { + "os2web/os2web_datalookup": { + "type": "vcs", + "url": "https://github.com/itk-dev/os2web_datalookup" + }, "drupal": { "type": "composer", "url": "https://packages.drupal.org/8" diff --git a/modules/os2forms_digital_post/README.md b/modules/os2forms_digital_post/README.md index c36e9f48..999d87c7 100644 --- a/modules/os2forms_digital_post/README.md +++ b/modules/os2forms_digital_post/README.md @@ -33,7 +33,8 @@ digital post. ### Key -We use [os2web_key](https://github.com/OS2web/os2web_key) to provide the certificate for sending digital post. +We use [os2web_key](https://github.com/OS2web/os2web_key) to provide the certificate for sending digital post, and the +key must be of type "[Certificate](https://github.com/os2web/os2web_key?tab=readme-ov-file#certificate)". ### Queue From b5fba51177a91ae2218d92487b3044b048db17a4 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 17 Dec 2024 23:26:21 +0100 Subject: [PATCH 12/88] Cleaned up composer.json --- composer.json | 48 ++++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/composer.json b/composer.json index ec6906b0..2b83bd51 100644 --- a/composer.json +++ b/composer.json @@ -56,8 +56,9 @@ "http-interop/http-factory-guzzle": "^1.0.0", "itk-dev/beskedfordeler-drupal": "^1.0", "itk-dev/serviceplatformen": "^1.5", + "mglaman/composer-drupal-lenient": "^1.0", "os2web/os2web_audit": "^0.1.6", - "os2web/os2web_datalookup": "dev-feature/os2web_key as 1.12.0", + "os2web/os2web_datalookup": "dev-feature/os2web_key as 2.0.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "php-http/guzzle7-adapter": "^1.0", @@ -65,8 +66,7 @@ "symfony/options-resolver": "^5.4 || ^6.0", "webmozart/path-util": "^2.3", "wsdltophp/packagebase": "^5.0", - "zaporylie/composer-drupal-optimizations": "^1.2", - "mglaman/composer-drupal-lenient": "^1.0" + "zaporylie/composer-drupal-optimizations": "^1.2" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", @@ -100,6 +100,7 @@ "cweagans/composer-patches": true, "dealerdirect/phpcodesniffer-composer-installer": true, "ergebnis/composer-normalize": true, + "mglaman/composer-drupal-lenient": true, "phpstan/extension-installer": true, "simplesamlphp/composer-module-installer": true, "vaimo/composer-patches": true, @@ -109,8 +110,19 @@ }, "extra": { "composer-exit-on-patch-failure": false, + "drupal-lenient": { + "allowed-list": [ + "drupal/coc_forms_auto_export", + "drupal/webform_node_element" + ] + }, "enable-patching": true, "patches": { + "drupal/coc_forms_auto_export": { + "3240592 - Problem with phpseclib requirement in 2.x (https://www.drupal.org/project/coc_forms_auto_export/issues/3240592)": "https://www.drupal.org/files/issues/2021-10-04/requirement-namespace-3240592-1.patch", + "3286562 - Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2022-06-15/coc_forms_auto_export.2.0.x-dev.rector.patch", + "3259009 - PHP Warnings/Notices on Download Page": "https://git.drupalcode.org/project/coc_forms_auto_export/-/merge_requests/1.diff" + }, "drupal/entity_print": { "2733781 - Add Export to Word Support": "https://www.drupal.org/files/issues/2019-11-22/2733781-47.patch" }, @@ -118,26 +130,14 @@ "Unlock possibility of using Entity print module export to Word": "https://www.drupal.org/files/issues/2020-02-29/3096552-6.patch", "Webform computed element post save alter": "https://www.drupal.org/files/issues/2024-06-25/webform_computed_post_save_field_alter.patch", "Add custom hook (hook_webform_post_load_data) for audit logging": "https://gist.githubusercontent.com/cableman/d26898fc8f65ee0a31001bf391583b59/raw/6189dc4c2ceaabb19d25cc4b98b0b3028a6b0e1e/gistfile1.txt" - - }, - "drupal/coc_forms_auto_export": { - "3240592 - Problem with phpseclib requirement in 2.x (https://www.drupal.org/project/coc_forms_auto_export/issues/3240592)": "https://www.drupal.org/files/issues/2021-10-04/requirement-namespace-3240592-1.patch", - "3286562 - Automated Drupal 10 compatibility fixes" : "https://www.drupal.org/files/issues/2022-06-15/coc_forms_auto_export.2.0.x-dev.rector.patch", - "3259009 - PHP Warnings/Notices on Download Page" : "https://git.drupalcode.org/project/coc_forms_auto_export/-/merge_requests/1.diff" - }, - "drupal/webform_node_element": { - "3290637 - Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2023-05-12/webform_node_element_d10-3290637-11.patch" }, "drupal/webform_encrypt": { "Ensure data is base64 encoded (https://www.drupal.org/project/webform_encrypt/issues/3399414)": "https://git.drupalcode.org/project/webform_encrypt/-/merge_requests/4.patch", "PHP Warning if unserialize fails (https://www.drupal.org/project/webform_encrypt/issues/3292305)": "https://www.drupal.org/files/issues/2022-06-23/unserialize-php-notice.patch" + }, + "drupal/webform_node_element": { + "3290637 - Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2023-05-12/webform_node_element_d10-3290637-11.patch" } - }, - "drupal-lenient": { - "allowed-list": [ - "drupal/coc_forms_auto_export", - "drupal/webform_node_element" - ] } }, "scripts": { @@ -159,17 +159,5 @@ "coding-standards-check/phpcs": [ "phpcs --standard=phpcs.xml.dist" ] - }, - "config": { - "sort-packages": true, - "allow-plugins": { - "cweagans/composer-patches": true, - "dealerdirect/phpcodesniffer-composer-installer": true, - "phpstan/extension-installer": true, - "simplesamlphp/composer-module-installer": true, - "vaimo/composer-patches": true, - "zaporylie/composer-drupal-optimizations": true, - "mglaman/composer-drupal-lenient": true - } } } From a8dc3c80c03c633c3a3c6b11849ad5c3739892f2 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 17 Dec 2024 23:40:20 +0100 Subject: [PATCH 13/88] Cleaned up --- README.md | 1 + modules/os2forms_fasit/docs/BENYTTELSE.md | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 90245a21..44eb8e47 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer c ### Markdown ```sh +docker pull peterdavehello/markdownlint docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' --fix docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' ``` diff --git a/modules/os2forms_fasit/docs/BENYTTELSE.md b/modules/os2forms_fasit/docs/BENYTTELSE.md index 63cc361d..cb8c958a 100644 --- a/modules/os2forms_fasit/docs/BENYTTELSE.md +++ b/modules/os2forms_fasit/docs/BENYTTELSE.md @@ -10,7 +10,7 @@ aftale hvilke certifikater der anvendes. Disse certifikater skal være OCES-3, f.eks. FOCES-3, og skal bruges i pem- eller cer-format. Dernæst oplyses det anvendte certifikats thumbprint eller public-key til Fasit, -som derefter aktiverer snitfladen. Se evt. +som derefter aktiverer snitfladen. Se evt. [README#certificate](../README.md#certificate) for hvordan et certifikats thumbprint kan findes gennem kommandolinjen. @@ -22,7 +22,7 @@ Her skal følgende sættes op: * Fasit API base url * Basis url’en til Fasit. Denne specificeres af Fasit. - * Eksempel: https://webservices.fasit.dk/ + * Eksempel: * Fasit API tenant * Fasit tenant. Denne specificeres af Fasit. * Eksempel: aarhus @@ -30,14 +30,14 @@ Her skal følgende sættes op: * Hvilken version af af API’et der skal bruges. Her er mulighederne ’v1’ eller ’v2’. Der bør altid bruges ’v2’. * Eksempel: v2 * Certificate - * Her kan angives detaljer til et azure key vault hvori certifikatet ligges (Azure key vault) eller en sti direkte til certifikatet (Filsystem) + * Her kan angives detaljer til et azure key vault hvori certifikatet ligges (Azure key vault) eller en sti direkte til + certifikatet (Filsystem) * Passphrase * Passphrase til certifikatet, hvis sådan et eksisterer. - Se evt. Fasit Scultz dokumentationen for flere detaljer på opbygningen af endpoint url’er. -Det er desuden muligt at teste om os2forms kan få fat i certifikatet på samme konfigurations-side. +Det er desuden muligt at teste om os2forms kan få fat i certifikatet på samme konfigurations-side. ## Handler From fea276dab167990c186a949bb0038935cd11fe0e Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 20 Dec 2024 15:27:57 +0100 Subject: [PATCH 14/88] Updated --- .github/workflows/pr.yml | 6 ++-- .../DataLookup/DatafordelerDataLookup.php | 28 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c78e9623..6eb23e77 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [ '8.1' ] + php-versions: [ '8.3' ] dependency-version: [ prefer-lowest, prefer-stable ] steps: - uses: actions/checkout@master @@ -59,7 +59,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [ '8.1' ] + php-versions: [ '8.3' ] steps: - uses: actions/checkout@master - name: Setup PHP, with composer and extensions @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [ '8.1' ] + php-versions: [ '8.3' ] steps: - uses: actions/checkout@master - name: Setup PHP, with composer and extensions diff --git a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php index ba699519..9d3b1aaa 100644 --- a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php +++ b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php @@ -3,8 +3,11 @@ namespace Drupal\os2forms_dawa\Plugin\os2web\DataLookup; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\File\FileSystem; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\key\KeyRepository; +use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_dawa\Entity\DatafordelerMatrikula; use Drupal\os2web_audit\Service\Logger; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupBase; @@ -21,13 +24,6 @@ */ class DatafordelerDataLookup extends DataLookupBase implements DatafordelerDataLookupInterface, ContainerFactoryPluginInterface { - /** - * The HTTP client to fetch the feed data with. - * - * @var \GuzzleHttp\ClientInterface - */ - protected $httpClient; - /** * {@inheritdoc} */ @@ -35,23 +31,33 @@ public function __construct( array $configuration, $plugin_id, $plugin_definition, - ClientInterface $httpClient, + protected ClientInterface $httpClient, Logger $auditLogger, + KeyRepositoryInterface $keyRepository, + FileSystem $fileSystem, ) { - $this->httpClient = $httpClient; - parent::__construct($configuration, $plugin_id, $plugin_definition, $auditLogger); + parent::__construct($configuration, $plugin_id, $plugin_definition, $auditLogger, $keyRepository, $fileSystem); } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + /** @var Logger $auditLogger */ + $auditLogger = $container->get('os2web_audit.logger'); + /** @var \Drupal\key\KeyRepositoryInterface $keyRepository */ + $keyRepository = $container->get('key.repository'); + /** @var \Drupal\Core\File\FileSystem $fileSystem */ + $fileSystem = $container->get('file_system'); + return new static( $configuration, $plugin_id, $plugin_definition, $container->get('http_client'), - $container->get('os2web_audit.logger'), + $auditLogger, + $keyRepository, + $fileSystem, ); } From 5d36f4a304e17ea667cebc77bfa2629f701fdb3b Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Fri, 27 Dec 2024 15:46:13 +0200 Subject: [PATCH 15/88] OS-145 adding websubmissions automatic cleaning --- .../os2forms_digital_signature.module | 11 +++ .../src/Form/SettingsForm.php | 6 ++ .../src/Service/SigningService.php | 68 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.module b/modules/os2forms_digital_signature/os2forms_digital_signature.module index 1f686baf..994aeba3 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.module +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.module @@ -4,6 +4,17 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\os2forms_digital_signature\Form\SettingsForm; +/** + * Implements hook_cron(). + * + * Deletes stalled webform submissions that were left unsigned. + */ +function os2forms_digital_signature_cron() { + /** @var \Drupal\os2forms_digital_signature\Service\SigningService $service */ + $service = \Drupal::service('os2forms_digital_signature.signing_service'); + $service->deleteStalledSubmissions(); +} + /** * Implements hook_webform_submission_form_alter(). * diff --git a/modules/os2forms_digital_signature/src/Form/SettingsForm.php b/modules/os2forms_digital_signature/src/Form/SettingsForm.php index 473f64ec..f40fbffb 100644 --- a/modules/os2forms_digital_signature/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_signature/src/Form/SettingsForm.php @@ -53,6 +53,12 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_submission_allowed_ips'), '#description' => t('Comma separated. Ex. 192.168.1.1,192.168.2.1'), ]; + $form['os2forms_digital_signature_submission_retention_period'] = [ + '#type' => 'textfield', + '#title' => t('Unsigned submission timespan (s)'), + '#default_value' => ($this->config(self::$configName)->get('os2forms_digital_signature_submission_retention_period')) ?? 300, + '#description' => t('How many seconds can unsigned submission exist before being automatically deleted'), + ]; return parent::buildForm($form, $form_state); } diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index d72ee120..ba7a67ee 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -5,7 +5,10 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ImmutableConfig; use Drupal\os2forms_digital_signature\Form\SettingsForm; +use Drupal\os2forms_digital_signature\Plugin\WebformHandler\DigitalSignatureWebformHandler; use Symfony\Component\HttpFoundation\RedirectResponse; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; class SigningService { @@ -123,4 +126,69 @@ private function getHash(string $value) : string { return sha1($hashSalt . $value); } + /** + * Deletes stalled webform submissions that were left unsigned. + * + * Only checked the webforms that have digital_signature handler enabled and the submission is older that a specified + * period. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + public function deleteStalledSubmissions() : void { + $digitalSignatureWebforms = []; + + // Finding webforms that have any handler. + $query = \Drupal::entityQuery('webform') + ->exists('handlers'); // Only webforms with handlers configured. + $handler_webform_ids = $query->execute(); + + // No webforms with handlers, aborting. + if (empty($handler_webform_ids)) { + return; + } + + // Find all with os2forms_digital_signature handlers enabled. + foreach ($handler_webform_ids as $webform_id) { + $webform = Webform::load($webform_id); + if (!$webform) { + continue; + } + + $handlers = $webform->getHandlers(); + foreach ($handlers as $handler) { + // Check if the handler is of type 'os2forms_digital_signature'. + if ($handler->getPluginId() === 'os2forms_digital_signature' && $handler->isEnabled()) { + $digitalSignatureWebforms[] = $webform->id(); + break; + } + } + } + + // No webforms, aborting. + if (empty($digitalSignatureWebforms)) { + return; + } + + // Find all stalled webform submissions of digital signature forms. + $retention_period = ($this->config->get('os2forms_digital_signature_submission_retention_period')) ?? 300; + $timestamp_threshold = \Drupal::time()->getRequestTime() - $retention_period; + $query = \Drupal::entityQuery('webform_submission') + ->accessCheck(FALSE) + ->condition('webform_id', $digitalSignatureWebforms, 'IN') + ->condition('locked', 0) + ->condition('created', $timestamp_threshold, '<'); + $submission_ids = $query->execute(); + + // No submissions, aborting. + if (empty($submission_ids)) { + return; + } + + // Deleting all stalled webform submissions. + foreach ($submission_ids as $submission_id) { + $submission = WebformSubmission::load($submission_id); + $submission->delete(); + } + } + } From d0cff5e95486add3a748a2980e6aa115dc841994 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Thu, 2 Jan 2025 11:35:18 +0100 Subject: [PATCH 16/88] ITKDEV: Updated fbs handler to use latest endpoints and operations --- CHANGELOG.md | 1 + .../os2forms_fbs_handler/src/Client/FBS.php | 104 ++++++++++++------ .../src/Client/Model/Guardian.php | 2 +- .../src/Client/Model/Patron.php | 11 +- .../AdvancedQueue/JobType/FbsCreateUser.php | 44 +++++--- 5 files changed, 112 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f03e6d0..3415627d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa - [#101](https://github.com/OS2Forms/os2forms/pull/101) Added support for os2web_key +- Updated `os2forms_fbs_handler` to use latest endpoints and operations. ## [3.21.0] 2024-12-17 diff --git a/modules/os2forms_fbs_handler/src/Client/FBS.php b/modules/os2forms_fbs_handler/src/Client/FBS.php index 660a83a0..6f33f22c 100644 --- a/modules/os2forms_fbs_handler/src/Client/FBS.php +++ b/modules/os2forms_fbs_handler/src/Client/FBS.php @@ -75,34 +75,22 @@ public function isLoggedIn(): bool { * @param string $cpr * The users personal security number. * - * @return \Drupal\os2forms_fbs_handler\Client\Model\Patron|null - * NULL if not else the Patron. + * @return string|null + * NULL if not else the PatronId. * * @throws \GuzzleHttp\Exception\GuzzleException * @throws \JsonException */ - public function doUserExists(string $cpr): ?Patron { + public function authenticatePatron(string $cpr): ?string { // Check if session has been created with FBS and if not creates it. if (!$this->isLoggedIn()) { $this->login(); } - // Try pre-authenticate the user/parent. - $json = $this->request('/external/{agency_id}/patrons/preauthenticated/v9', $cpr); + // Authenticate the patron. + $json = $this->request('/external/{agency_id}/patrons/preauthenticated/v10', $cpr); if ($json->authenticateStatus === $this::AUTHENTICATE_STATUS_VALID) { - return new Patron( - $json->patron->patronId, - (bool) $json->patron->receiveSms, - (bool) $json->patron->receivePostalMail, - $json->patron->notificationProtocols, - $json->patron->phoneNumber, - is_null($json->patron->onHold) ? $json->patron->onHold : (array) $json->patron->onHold, - $json->patron->preferredLanguage, - (bool) $json->patron->guardianVisibility, - $json->patron->emailAddress, - (bool) $json->patron->receiveEmail, - $json->patron->preferredPickupBranch - ); + return $json->patronId; } return NULL; @@ -123,19 +111,61 @@ public function doUserExists(string $cpr): ?Patron { * @throws \JsonException */ public function createPatronWithGuardian(Patron $patron, Guardian $guardian) { - $uri = '/external/{agency_id}/patrons/withGuardian/v1'; + $uri = '/external/{agency_id}/patrons/withGuardian/v4'; $payload = [ - 'cprNumber' => $patron->cpr, + 'personId' => $patron->personId, 'pincode' => $patron->pincode, 'preferredPickupBranch' => $patron->preferredPickupBranch, 'name' => 'Unknown Name', - 'email' => $patron->emailAddress, + 'emailAddresses' => $patron->emailAddresses, 'guardian' => $guardian->toArray(), ]; - return $this->request($uri, $payload,); + return $this->request($uri, $payload); } + /** + * Get patron information. + * + * @param string $patronId + * The patron to update. + * + * @return \Drupal\os2forms_fbs_handler\Client\Model\Patron + * Patron object + * + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \JsonException + */ + public function getPatron(string $patronId): ?Patron { + $uri = '/external/{agency_id}/patrons/' . $patronId . '/v4'; + + $json = $this->request($uri, [], RequestMethodInterface::METHOD_GET); + + if ($json->authenticateStatus === "VALID") { + return new Patron( + $json->patron->patronId, + (bool) $json->patron->receiveSms, + (bool) $json->patron->receivePostalMail, + $json->patron->notificationProtocols, + $json->patron->phoneNumber, + is_null($json->patron->onHold) ? $json->patron->onHold : (array) $json->patron->onHold, + $json->patron->preferredLanguage, + (bool) $json->patron->guardianVisibility, + $json->patron->defaultInterestPeriod, + (bool) $json->patron->resident, + [ + [ + 'emailAddress' => $json->patron->emailAddress, + 'receiveNotification' => $json->patron->receiveEmail, + ], + ], + (bool) $json->patron->receiveEmail, + $json->patron->preferredPickupBranch + ); + } + return NULL; + } + /** * Update patron information. * @@ -149,19 +179,27 @@ public function createPatronWithGuardian(Patron $patron, Guardian $guardian) { * @throws \JsonException */ public function updatePatron(Patron $patron): bool { - $uri = '/external/{agency_id}/patrons/' . $patron->patronId . '/v6'; + $uri = '/external/{agency_id}/patrons/' . $patron->patronId . '/v8'; $payload = [ - 'patronid' => $patron->patronId, - 'patron' => $patron->toArray(), + 'patron' => [ + 'preferredPickupBranch' => $patron->preferredPickupBranch, + 'emailAddresses' => $patron->emailAddresses, + 'guardianVisibility' => $patron->guardianVisibility, + 'receivePostalMail' => $patron->receiveEmail, + 'phoneNumbers' => [ + [ + 'receiveNotification' => TRUE, + 'phoneNumber' => $patron->phoneNumber, + ], + ], + ], 'pincodeChange' => [ 'pincode' => $patron->pincode, - 'libraryCardNumber' => $patron->cpr, + 'libraryCardNumber' => $patron->personId, ], ]; - $json = $this->request($uri, $payload, Request::METHOD_PUT); - - return $json->authenticateStatus === $this::AUTHENTICATE_STATUS_VALID; + return $this->request($uri, $payload, RequestMethodInterface::METHOD_PUT); } /** @@ -179,7 +217,7 @@ public function updatePatron(Patron $patron): bool { * @throws \JsonException */ public function createGuardian(Patron $patron, Guardian $guardian): int { - $uri = '/external/{agency_id}/patrons/withGuardian/v1'; + $uri = '/external/{agency_id}/patrons/withGuardian/v2'; $payload = [ 'patronId' => $patron->patronId, 'guardian' => $guardian->toArray(), @@ -199,7 +237,7 @@ public function createGuardian(Patron $patron, Guardian $guardian): int { * The type of request to send (Default: POST). * * @return mixed - * Json response from FBS. + * Json response from FBS or TRUE on updatePatron response. * * @throws \GuzzleHttp\Exception\GuzzleException * @throws \JsonException @@ -230,6 +268,10 @@ private function request(string $uri, array|string $data, string $method = Reque $response = $this->client->request($method, $url, $options); + if ($response->getStatusCode() === 204) { + return TRUE; + } + return json_decode($response->getBody(), FALSE, 512, JSON_THROW_ON_ERROR); } diff --git a/modules/os2forms_fbs_handler/src/Client/Model/Guardian.php b/modules/os2forms_fbs_handler/src/Client/Model/Guardian.php index 150db93e..371d3bba 100644 --- a/modules/os2forms_fbs_handler/src/Client/Model/Guardian.php +++ b/modules/os2forms_fbs_handler/src/Client/Model/Guardian.php @@ -25,7 +25,7 @@ public function __construct( */ public function toArray(): array { return [ - 'cprNumber' => $this->cpr, + 'personIdentifier' => $this->cpr, 'name' => $this->name, 'email' => $this->email, ]; diff --git a/modules/os2forms_fbs_handler/src/Client/Model/Patron.php b/modules/os2forms_fbs_handler/src/Client/Model/Patron.php index 4847c944..e5e7d562 100644 --- a/modules/os2forms_fbs_handler/src/Client/Model/Patron.php +++ b/modules/os2forms_fbs_handler/src/Client/Model/Patron.php @@ -19,11 +19,13 @@ public function __construct( public readonly ?array $onHold = NULL, public readonly ?string $preferredLanguage = NULL, public readonly ?bool $guardianVisibility = NULL, + public readonly ?int $defaultInterestPeriod = NULL, + public readonly ?bool $resident = NULL, // Allow these properties below to be updatable. - public ?string $emailAddress = NULL, + public ?array $emailAddresses = NULL, public ?bool $receiveEmail = NULL, public ?string $preferredPickupBranch = NULL, - public ?string $cpr = NULL, + public ?string $personId = NULL, public ?string $pincode = NULL, ) { } @@ -36,16 +38,19 @@ public function __construct( */ public function toArray(): array { return [ + 'patronId' => $this->patronId, 'receiveEmail' => $this->receiveEmail, 'receiveSms' => $this->receiveSms, 'receivePostalMail' => $this->receivePostalMail, - 'emailAddress' => $this->emailAddress, + 'emailAddresses' => $this->emailAddresses, 'notificationProtocols' => $this->notificationProtocols, 'phoneNumber' => $this->phoneNumber, 'preferredPickupBranch' => $this->preferredPickupBranch, 'onHold' => $this->onHold, 'preferredLanguage' => $this->preferredLanguage, 'guardianVisibility' => $this->guardianVisibility, + 'defaultInterestPeriod' => $this->defaultInterestPeriod, + 'resident' => $this->resident, ]; } diff --git a/modules/os2forms_fbs_handler/src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php b/modules/os2forms_fbs_handler/src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php index 3afe322c..179d0a1a 100644 --- a/modules/os2forms_fbs_handler/src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php +++ b/modules/os2forms_fbs_handler/src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php @@ -87,9 +87,6 @@ public function process(Job $job): JobResult { $data = $webformSubmission->getData(); - // Checker child patron exists. - $patron = $fbs->doUserExists($data['barn_cpr']); - // Create Guardian. $guardian = new Guardian( $data['cpr'], @@ -97,26 +94,43 @@ public function process(Job $job): JobResult { $data['email'] ); + // Check if child patron exists. + $patronId = $fbs->authenticatePatron($data['barn_cpr']); + // If "yes" update the child patron and create the guardian (the // guardian is not another patron user). - if (!is_null($patron)) { - // Create Patron object with updated values. - $patron->preferredPickupBranch = $data['afhentningssted']; - $patron->emailAddress = $data['barn_mail']; - $patron->receiveEmail = TRUE; - $patron->cpr = $data['barn_cpr']; - $patron->pincode = $data['pinkode']; - - $fbs->updatePatron($patron); - $fbs->createGuardian($patron, $guardian); + if (!is_null($patronId)) { + // Fetch patron. + $patron = $fbs->getPatron($patronId); + + if (!is_null($patron)) { + // Create Patron object with updated values. + $patron->preferredPickupBranch = $data['afhentningssted']; + $patron->emailAddresses = [ + [ + 'emailAddress' => $data['barn_mail'], + 'receiveNotification' => TRUE, + ], + ]; + $patron->receiveEmail = TRUE; + $patron->pincode = $data['pinkode']; + + $fbs->updatePatron($patron); + $fbs->createGuardian($patron, $guardian); + } } else { // If "no" create child patron and guardian. $patron = new Patron(); $patron->preferredPickupBranch = $data['afhentningssted']; - $patron->emailAddress = $data['barn_mail']; + $patron->emailAddresses = [ + [ + 'emailAddress' => $data['barn_mail'], + 'receiveNotification' => TRUE, + ], + ]; $patron->receiveEmail = TRUE; - $patron->cpr = $data['barn_cpr']; + $patron->personId = $data['barn_cpr']; $patron->pincode = $data['pinkode']; $fbs->createPatronWithGuardian($patron, $guardian); From 1f3a49e208fb889655ffcd6e1ff82083d39a233d Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Thu, 2 Jan 2025 12:29:39 +0100 Subject: [PATCH 17/88] ITKDEV: Adjust indention --- .../os2forms_fbs_handler/src/Client/FBS.php | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/modules/os2forms_fbs_handler/src/Client/FBS.php b/modules/os2forms_fbs_handler/src/Client/FBS.php index 6f33f22c..44b8f6a7 100644 --- a/modules/os2forms_fbs_handler/src/Client/FBS.php +++ b/modules/os2forms_fbs_handler/src/Client/FBS.php @@ -90,7 +90,7 @@ public function authenticatePatron(string $cpr): ?string { // Authenticate the patron. $json = $this->request('/external/{agency_id}/patrons/preauthenticated/v10', $cpr); if ($json->authenticateStatus === $this::AUTHENTICATE_STATUS_VALID) { - return $json->patronId; + return $json->patronId; } return NULL; @@ -124,48 +124,49 @@ public function createPatronWithGuardian(Patron $patron, Guardian $guardian) { return $this->request($uri, $payload); } - /** - * Get patron information. - * - * @param string $patronId - * The patron to update. - * - * @return \Drupal\os2forms_fbs_handler\Client\Model\Patron - * Patron object - * - * @throws \GuzzleHttp\Exception\GuzzleException - * @throws \JsonException - */ - public function getPatron(string $patronId): ?Patron { - $uri = '/external/{agency_id}/patrons/' . $patronId . '/v4'; - - $json = $this->request($uri, [], RequestMethodInterface::METHOD_GET); - - if ($json->authenticateStatus === "VALID") { - return new Patron( - $json->patron->patronId, - (bool) $json->patron->receiveSms, - (bool) $json->patron->receivePostalMail, - $json->patron->notificationProtocols, - $json->patron->phoneNumber, - is_null($json->patron->onHold) ? $json->patron->onHold : (array) $json->patron->onHold, - $json->patron->preferredLanguage, - (bool) $json->patron->guardianVisibility, - $json->patron->defaultInterestPeriod, - (bool) $json->patron->resident, + /** + * Get patron information. + * + * @param string $patronId + * The patron to update. + * + * @return \Drupal\os2forms_fbs_handler\Client\Model\Patron + * Patron object + * + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \JsonException + */ + public function getPatron(string $patronId): ?Patron { + $uri = '/external/{agency_id}/patrons/' . $patronId . '/v4'; + + $json = $this->request($uri, [], RequestMethodInterface::METHOD_GET); + + if ($json->authenticateStatus === "VALID") { + return new Patron( + $json->patron->patronId, + (bool) $json->patron->receiveSms, + (bool) $json->patron->receivePostalMail, + $json->patron->notificationProtocols, + $json->patron->phoneNumber, + is_null($json->patron->onHold) ? $json->patron->onHold : (array) $json->patron->onHold, + $json->patron->preferredLanguage, + (bool) $json->patron->guardianVisibility, + $json->patron->defaultInterestPeriod, + (bool) $json->patron->resident, + [ [ - [ - 'emailAddress' => $json->patron->emailAddress, - 'receiveNotification' => $json->patron->receiveEmail, - ], + 'emailAddress' => $json->patron->emailAddress, + 'receiveNotification' => $json->patron->receiveEmail, ], - (bool) $json->patron->receiveEmail, - $json->patron->preferredPickupBranch - ); - } - return NULL; + ], + (bool) $json->patron->receiveEmail, + $json->patron->preferredPickupBranch + ); } + return NULL; + } + /** * Update patron information. * From 2447c985e7d6516ac8b7639ede80d5b7f1c86703 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 3 Jan 2025 09:05:14 +0100 Subject: [PATCH 18/88] ITKDEV: Used configured format in maestro notification --- CHANGELOG.md | 1 + .../src/Plugin/WebformHandler/MaestroNotificationHandler.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3415627d..722fa709 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa - [#101](https://github.com/OS2Forms/os2forms/pull/101) Added support for os2web_key +- Updated Maestro notification handler assignment message format. - Updated `os2forms_fbs_handler` to use latest endpoints and operations. ## [3.21.0] 2024-12-17 diff --git a/modules/os2forms_forloeb/src/Plugin/WebformHandler/MaestroNotificationHandler.php b/modules/os2forms_forloeb/src/Plugin/WebformHandler/MaestroNotificationHandler.php index 156cc181..18f2a4eb 100644 --- a/modules/os2forms_forloeb/src/Plugin/WebformHandler/MaestroNotificationHandler.php +++ b/modules/os2forms_forloeb/src/Plugin/WebformHandler/MaestroNotificationHandler.php @@ -24,6 +24,7 @@ final class MaestroNotificationHandler extends WebformHandlerBase { public const NOTIFICATION = 'notification'; public const TYPE = 'type'; + public const FORMAT = 'format'; public const SENDER_LABEL = 'sender_label'; public const NOTIFICATION_ENABLE = 'notification_enable'; public const NOTIFICATION_RECIPIENT = 'notification_recipient'; @@ -153,7 +154,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $formStat } $form[self::NOTIFICATION][$notificationType][self::NOTIFICATION_CONTENT] = [ '#type' => 'text_format', - '#format' => 'restricted_html', + '#format' => $this->configuration[self::NOTIFICATION][$notificationType][self::NOTIFICATION_CONTENT][self::FORMAT] ?? 'restricted_html', '#title' => $this->t('Message'), '#default_value' => $content ?? self::TOKEN_MAESTRO_TASK_URL, '#description' => $this->t('The actual notification content. Must contain the @token_maestro_task_url token which is the URL to the Maestro task.', From 2308816696aac8ac2ae8aed7caa905f0bbe081e5 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 6 Jan 2025 09:51:16 +0100 Subject: [PATCH 19/88] Release 3.21.1 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 722fa709..d30a1b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ before starting to add changes. Use example [placed in the end of the page](#exa - [#101](https://github.com/OS2Forms/os2forms/pull/101) Added support for os2web_key + +## [3.21.1] 2025-01-06 + - Updated Maestro notification handler assignment message format. - Updated `os2forms_fbs_handler` to use latest endpoints and operations. @@ -328,7 +331,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa - Security in case of vulnerabilities. ``` -[Unreleased]: https://github.com/OS2Forms/os2forms/compare/3.21.0...HEAD +[Unreleased]: https://github.com/OS2Forms/os2forms/compare/3.21.1...HEAD +[3.21.1]: https://github.com/OS2Forms/os2forms/compare/3.21.0...3.21.1 [3.21.0]: https://github.com/OS2Forms/os2forms/compare/3.20.1...3.21.0 [3.20.1]: https://github.com/OS2Forms/os2forms/compare/3.20.0...3.20.1 [3.20.0]: https://github.com/OS2Forms/os2forms/compare/3.19.0...3.20.0 From 1ff2132dbdb886a4144a2f4dbd234691a6366788 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 17 Jan 2025 11:37:03 +0100 Subject: [PATCH 20/88] Support for os2web_key in DigitalPost and Fasit --- CHANGELOG.md | 3 +- .../os2forms_digital_post/drush.services.yml | 3 +- .../os2forms_digital_post.services.yml | 2 +- .../Commands/DigitalPostTestCommands.php | 2 +- modules/os2forms_fasit/README.md | 24 +- modules/os2forms_fasit/drush.services.yml | 6 + .../os2forms_fasit/os2forms_fasit.info.yml | 1 + modules/os2forms_fasit/os2forms_fasit.install | 15 ++ .../os2forms_fasit.services.yml | 9 +- .../src/Drush/Commands/FasitTestCommands.php | 38 ++++ .../os2forms_fasit/src/Form/SettingsForm.php | 207 ++++++------------ .../src/Helper/CertificateLocatorHelper.php | 1 + .../os2forms_fasit/src/Helper/FasitHelper.php | 100 +++++---- .../os2forms_fasit/src/Helper/Settings.php | 100 +++------ 14 files changed, 251 insertions(+), 260 deletions(-) rename modules/os2forms_digital_post/src/{Drush => }/Commands/DigitalPostTestCommands.php (98%) create mode 100644 modules/os2forms_fasit/drush.services.yml create mode 100644 modules/os2forms_fasit/os2forms_fasit.install create mode 100644 modules/os2forms_fasit/src/Drush/Commands/FasitTestCommands.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d30a1b99..2feae1a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] - [#101](https://github.com/OS2Forms/os2forms/pull/101) - Added support for os2web_key + - Added support for `os2web_key` in Digital post + - Added support for `os2web_key` in Fasit handler. ## [3.21.1] 2025-01-06 diff --git a/modules/os2forms_digital_post/drush.services.yml b/modules/os2forms_digital_post/drush.services.yml index ac0d5f8e..7339bc2a 100644 --- a/modules/os2forms_digital_post/drush.services.yml +++ b/modules/os2forms_digital_post/drush.services.yml @@ -1,5 +1,6 @@ services: - Drupal\os2forms_digital_post\Drush\Commands\DigitalPostTestCommands: + os2forms_digital_post.commands: + class: \Drupal\os2forms_digital_post\Commands\DigitalPostTestCommands arguments: - '@Drupal\os2forms_digital_post\Helper\DigitalPostHelper' - '@token' diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 9b096fad..31ff12ef 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -32,7 +32,7 @@ services: Drupal\os2forms_digital_post\Helper\DigitalPostHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@Drupal\\os2web_key\\KeyHelper" + - "@os2web_key.key_helper" - "@plugin.manager.os2web_datalookup" - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" diff --git a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php b/modules/os2forms_digital_post/src/Commands/DigitalPostTestCommands.php similarity index 98% rename from modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php rename to modules/os2forms_digital_post/src/Commands/DigitalPostTestCommands.php index 8d7d17c0..5e9bdbbf 100644 --- a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php +++ b/modules/os2forms_digital_post/src/Commands/DigitalPostTestCommands.php @@ -1,6 +1,6 @@ install([ + 'key', + ], TRUE); +} diff --git a/modules/os2forms_fasit/os2forms_fasit.services.yml b/modules/os2forms_fasit/os2forms_fasit.services.yml index 4397c83f..e6fe5e8c 100644 --- a/modules/os2forms_fasit/os2forms_fasit.services.yml +++ b/modules/os2forms_fasit/os2forms_fasit.services.yml @@ -1,16 +1,13 @@ services: Drupal\os2forms_fasit\Helper\Settings: arguments: - - "@keyvalue" - - Drupal\os2forms_fasit\Helper\CertificateLocatorHelper: - arguments: - - "@Drupal\\os2forms_fasit\\Helper\\Settings" + - "@config.factory" + - "@key.repository" Drupal\os2forms_fasit\Helper\FasitHelper: arguments: - '@http_client' - '@entity_type.manager' - "@Drupal\\os2forms_fasit\\Helper\\Settings" - - "@Drupal\\os2forms_fasit\\Helper\\CertificateLocatorHelper" + - "@file_system" - "@os2web_audit.logger" diff --git a/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommands.php b/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommands.php new file mode 100644 index 00000000..4ac71534 --- /dev/null +++ b/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommands.php @@ -0,0 +1,38 @@ +helper->pingApi(); + $this->io()->success('Successfully connected to Fasit API'); + } + catch (\Throwable $t) { + $this->io()->error($t->getMessage()); + } + + } + +} diff --git a/modules/os2forms_fasit/src/Form/SettingsForm.php b/modules/os2forms_fasit/src/Form/SettingsForm.php index d0fe119e..f703255c 100644 --- a/modules/os2forms_fasit/src/Form/SettingsForm.php +++ b/modules/os2forms_fasit/src/Form/SettingsForm.php @@ -2,41 +2,61 @@ namespace Drupal\os2forms_fasit\Form; -use Drupal\Core\Form\FormBase; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\os2forms_fasit\Helper\CertificateLocatorHelper; -use Drupal\os2forms_fasit\Helper\Settings; +use Drupal\os2forms_fasit\Helper\FasitHelper; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\OptionsResolver\Exception\ExceptionInterface as OptionsResolverException; /** - * Organisation settings form. + * Fasit settings form. */ -final class SettingsForm extends FormBase { +final class SettingsForm extends ConfigFormBase { use StringTranslationTrait; + public const CONFIG_NAME = 'os2forms_fasit.settings'; public const FASIT_API_BASE_URL = 'fasit_api_base_url'; public const FASIT_API_TENANT = 'fasit_api_tenant'; public const FASIT_API_VERSION = 'fasit_api_version'; public const CERTIFICATE = 'certificate'; + public const KEY = 'key'; + + public const ACTION_PING_API = 'action_ping_api'; /** - * Constructor. + * {@inheritdoc} */ - public function __construct(private readonly Settings $settings, private readonly CertificateLocatorHelper $certificateLocatorHelper) { + public function __construct( + ConfigFactoryInterface $config_factory, + private readonly FasitHelper $helper, + ) { + parent::__construct($config_factory); } /** * {@inheritdoc} + * + * @phpstan-return self */ - public static function create(ContainerInterface $container): SettingsForm { + public static function create(ContainerInterface $container): self { return new static( - $container->get(Settings::class), - $container->get(CertificateLocatorHelper::class) + $container->get('config.factory'), + $container->get(FasitHelper::class) ); } + /** + * {@inheritdoc} + * + * @phpstan-return array + */ + protected function getEditableConfigNames() { + return [ + self::CONFIG_NAME, + ]; + } + /** * {@inheritdoc} */ @@ -51,116 +71,57 @@ public function getFormId() { * @phpstan-return array */ public function buildForm(array $form, FormStateInterface $form_state): array { + $form = parent::buildForm($form, $form_state); + $config = $this->config(self::CONFIG_NAME); - $fasitApiBaseUrl = $this->settings->getFasitApiBaseUrl(); $form[self::FASIT_API_BASE_URL] = [ '#type' => 'textfield', '#title' => $this->t('Fasit API base url'), '#required' => TRUE, - '#default_value' => !empty($fasitApiBaseUrl) ? $fasitApiBaseUrl : NULL, + '#default_value' => $config->get(self::FASIT_API_BASE_URL), '#description' => $this->t('Specifies which base url to use. This is disclosed by Schultz'), ]; - $fasitApiTenant = $this->settings->getFasitApiTenant(); $form[self::FASIT_API_TENANT] = [ '#type' => 'textfield', '#title' => $this->t('Fasit API tenant'), '#required' => TRUE, - '#default_value' => !empty($fasitApiTenant) ? $fasitApiTenant : NULL, + '#default_value' => $config->get(self::FASIT_API_TENANT), '#description' => $this->t('Specifies which tenant to use. This is disclosed by Schultz'), ]; - $fasitApiVersion = $this->settings->getFasitApiVersion(); $form[self::FASIT_API_VERSION] = [ '#type' => 'textfield', '#title' => $this->t('Fasit API version'), '#required' => TRUE, - '#default_value' => !empty($fasitApiVersion) ? $fasitApiVersion : NULL, + '#default_value' => $config->get(self::FASIT_API_VERSION), '#description' => $this->t('Specifies which api version to use. Should probably be v2'), ]; - $certificate = $this->settings->getCertificate(); - - $form[self::CERTIFICATE] = [ - '#type' => 'fieldset', - '#title' => $this->t('Certificate'), - '#tree' => TRUE, - - CertificateLocatorHelper::LOCATOR_TYPE => [ - '#type' => 'select', - '#title' => $this->t('Certificate locator type'), - '#options' => [ - CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT => $this->t('Azure key vault'), - CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM => $this->t('File system'), - ], - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_TYPE] ?? NULL, + $form[self::KEY] = [ + '#type' => 'key_select', + '#key_filters' => [ + 'type' => 'os2web_key_certificate', ], + '#title' => $this->t('Key'), + '#required' => TRUE, + '#default_value' => $config->get(self::KEY), ]; - $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT] = [ - '#type' => 'fieldset', - '#title' => $this->t('Azure key vault'), - '#states' => [ - 'visible' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT]], - ], - ]; - - $settings = [ - CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_TENANT_ID => ['title' => $this->t('Tenant id')], - CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_APPLICATION_ID => ['title' => $this->t('Application id')], - CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_CLIENT_SECRET => ['title' => $this->t('Client secret')], - CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_NAME => ['title' => $this->t('Name')], - CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_SECRET => ['title' => $this->t('Secret')], - CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_VERSION => ['title' => $this->t('Version')], - ]; - - foreach ($settings as $key => $info) { - $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] = [ - '#type' => 'textfield', - '#title' => $info['title'], - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] ?? NULL, - '#states' => [ - 'required' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT]], - ], - ]; - } + $form['actions']['ping_api'] = [ + '#type' => 'container', - $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM] = [ - '#type' => 'fieldset', - '#title' => $this->t('File system'), - '#states' => [ - 'visible' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]], + self::ACTION_PING_API => [ + '#type' => 'submit', + '#name' => self::ACTION_PING_API, + '#value' => $this->t('Ping API'), ], - 'path' => [ - '#type' => 'textfield', - '#title' => $this->t('Path'), - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]['path'] ?? NULL, - '#states' => [ - 'required' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]], - ], + 'message' => [ + '#markup' => $this->t('Note: Pinging the API will use saved config.'), ], ]; - $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_PASSPHRASE] = [ - '#type' => 'textfield', - '#title' => $this->t('Passphrase'), - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_PASSPHRASE] ?? NULL, - ]; - - $form['actions']['#type'] = 'actions'; - - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Save settings'), - ]; - - $form['actions']['testCertificate'] = [ - '#type' => 'submit', - '#name' => 'testCertificate', - '#value' => $this->t('Test certificate'), - ]; - return $form; } @@ -169,20 +130,12 @@ public function buildForm(array $form, FormStateInterface $form_state): array { * * @phpstan-param array $form */ - public function validateForm(array &$form, FormStateInterface $formState): void { - $triggeringElement = $formState->getTriggeringElement(); - if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { + public function validateForm(array &$form, FormStateInterface $form_state): void { + if (self::ACTION_PING_API === ($form_state->getTriggeringElement()['#name'] ?? NULL)) { return; } - $values = $formState->getValues(); - - if (CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM === $values[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE]) { - $path = $values[self::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])); - } - } + parent::validateForm($form, $form_state); } /** @@ -190,44 +143,30 @@ public function validateForm(array &$form, FormStateInterface $formState): void * * @phpstan-param array $form */ - public function submitForm(array &$form, FormStateInterface $formState): void { - $triggeringElement = $formState->getTriggeringElement(); - if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { - $this->testCertificate(); + public function submitForm(array &$form, FormStateInterface $form_state): void { + if (self::ACTION_PING_API === ($form_state->getTriggeringElement()['#name'] ?? NULL)) { + try { + $this->helper->pingApi(); + $this->messenger()->addStatus($this->t('Pinged API successfully.')); + } + catch (\Throwable $t) { + $this->messenger()->addError($this->t('Pinging API failed: @message', ['@message' => $t->getMessage()])); + } return; } - try { - $settings[self::CERTIFICATE] = $formState->getValue(self::CERTIFICATE); - $settings[self::FASIT_API_BASE_URL] = $formState->getValue(self::FASIT_API_BASE_URL); - $settings[self::FASIT_API_TENANT] = $formState->getValue(self::FASIT_API_TENANT); - $settings[self::FASIT_API_VERSION] = $formState->getValue(self::FASIT_API_VERSION); - - $this->settings->setSettings($settings); - $this->messenger()->addStatus($this->t('Settings saved')); + $config = $this->config(self::CONFIG_NAME); + foreach ([ + self::FASIT_API_BASE_URL, + self::FASIT_API_TENANT, + self::FASIT_API_VERSION, + self::KEY, + ] as $key) { + $config->set($key, $form_state->getValue($key)); } - catch (OptionsResolverException $exception) { - $this->messenger()->addError($this->t('Settings not saved (@message)', ['@message' => $exception->getMessage()])); - - return; - } - - $this->messenger()->addStatus($this->t('Settings saved')); - } + $config->save(); - /** - * Test certificate. - */ - private function testCertificate(): void { - try { - $certificateLocator = $this->certificateLocatorHelper->getCertificateLocator(); - $certificateLocator->getCertificates(); - $this->messenger()->addStatus($this->t('Certificate successfully tested')); - } - catch (\Throwable $throwable) { - $message = $this->t('Error testing certificate: %message', ['%message' => $throwable->getMessage()]); - $this->messenger()->addError($message); - } + parent::submitForm($form, $form_state); } } diff --git a/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php index 3f244d1a..bca6e3d1 100644 --- a/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php +++ b/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php @@ -18,6 +18,7 @@ class CertificateLocatorHelper { public const LOCATOR_TYPE = 'locator_type'; public const LOCATOR_TYPE_AZURE_KEY_VAULT = 'azure_key_vault'; + public const LOCATOR_TYPE_HASHICORP_KEY_VAULT = 'hashicorp_key_vault'; public const LOCATOR_TYPE_FILE_SYSTEM = 'file_system'; public const LOCATOR_PASSPHRASE = 'passphrase'; public const LOCATOR_AZURE_KEY_VAULT_TENANT_ID = 'tenant_id'; diff --git a/modules/os2forms_fasit/src/Helper/FasitHelper.php b/modules/os2forms_fasit/src/Helper/FasitHelper.php index 2bd84d65..25a3a4db 100644 --- a/modules/os2forms_fasit/src/Helper/FasitHelper.php +++ b/modules/os2forms_fasit/src/Helper/FasitHelper.php @@ -4,6 +4,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\File\FileSystemInterface; use Drupal\os2forms_attachment\Element\AttachmentElement; use Drupal\os2forms_fasit\Exception\FasitResponseException; use Drupal\os2forms_fasit\Exception\FasitXMLGenerationException; @@ -16,6 +17,7 @@ use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Psr7\Utils; +use Psr\Http\Message\ResponseInterface; use Symfony\Component\HttpFoundation\Response; /** @@ -38,7 +40,7 @@ public function __construct( private readonly ClientInterface $client, private readonly EntityTypeManagerInterface $entityTypeManager, private readonly Settings $settings, - private readonly CertificateLocatorHelper $certificateLocator, + private readonly FileSystemInterface $fileSystem, private readonly Logger $auditLogger, ) { } @@ -206,8 +208,6 @@ private function uploadDocument(array $uploads, string $submissionId, array $han } } - [$certificateOptions, $tempCertFilename] = $this->getCertificateOptionsAndTempCertFilename(); - $body = $doc->saveXML(); if (!$body) { @@ -219,20 +219,15 @@ private function uploadDocument(array $uploads, string $submissionId, array $han 'Content-Type' => 'application/xml', ], 'body' => $body, - 'cert' => $certificateOptions, + 'cert' => $this->settings->getCertificate(), ]; // Attempt upload. try { - $response = $this->client->request('POST', $endpoint, $options); + $response = $this->post($endpoint, $options); } catch (GuzzleException $e) { throw new FasitResponseException($e->getMessage(), $e->getCode()); - } finally { - // Remove the certificate from disk. - if (file_exists($tempCertFilename)) { - unlink($tempCertFilename); - } } if (Response::HTTP_OK !== $response->getStatusCode()) { @@ -262,26 +257,6 @@ private function checkHandlerConfiguration(array $handlerConfiguration, string $ } } - /** - * Gets certificate options and temp certificate filename. - * - * @throws \Drupal\os2forms_fasit\Exception\CertificateLocatorException - * Certificate locator exception. - * - * @phpstan-return array - */ - private function getCertificateOptionsAndTempCertFilename(): array { - $certificateLocator = $this->certificateLocator->getCertificateLocator(); - $localCertFilename = tempnam(sys_get_temp_dir(), 'cert'); - file_put_contents($localCertFilename, $certificateLocator->getCertificate()); - $certificateOptions = - $certificateLocator->hasPassphrase() ? - [$localCertFilename, $certificateLocator->getPassphrase()] - : $localCertFilename; - - return [$certificateOptions, $localCertFilename]; - } - /** * Uploads attachment to Fasit. * @@ -345,8 +320,6 @@ private function uploadFile(string $originalFilename, string $tempFilename, stri self::FASIT_API_METHOD_UPLOAD ); - [$certificateOptions, $tempCertFilename] = $this->getCertificateOptionsAndTempCertFilename(); - // Attempt upload. try { $options = [ @@ -356,18 +329,13 @@ private function uploadFile(string $originalFilename, string $tempFilename, stri 'X-Title' => pathinfo($originalFilename, PATHINFO_FILENAME), ], 'body' => Utils::tryFopen($tempFilename, 'r'), - 'cert' => $certificateOptions, ]; - $response = $this->client->request('POST', $endpoint, $options); + $response = $this->post($endpoint, $options); } catch (GuzzleException $e) { throw new FasitResponseException($e->getMessage(), $e->getCode()); } finally { - // Remove the certificate from disk. - if (file_exists($tempCertFilename)) { - unlink($tempCertFilename); - } // Remove the attachment from disk. if (file_exists($tempFilename)) { unlink($tempFilename); @@ -510,4 +478,60 @@ private function getSubmission(string $submissionId): EntityInterface { return $storage->load($submissionId); } + /** + * Send POST request to Fasit API. + * + * @param string $endpoint + * The API endpoint. + * @param array $options + * The request options. + * + * @return \Psr\Http\Message\ResponseInterface + * The response. + * + * @throws \GuzzleHttp\Exception\GuzzleException + * A Guzzle exception. + */ + private function post(string $endpoint, array $options): ResponseInterface { + try { + $certificate = $this->settings->getCertificate(); + $certPath = $this->fileSystem->tempnam($this->fileSystem->getTempDirectory(), 'os2forms_fasit_cert'); + // `tempnam` has created a file, so we must replace when saving. + $this->fileSystem->saveData($certificate, $certPath, FileSystemInterface::EXISTS_REPLACE); + $options['cert'] = $certPath; + + return $this->client->request('POST', $endpoint, $options); + } finally { + // Remove the certificate from disk. + if (isset($certPath) && file_exists($certPath)) { + unlink($certPath); + } + } + } + + /** + * Ping the Fasit API and expect a 400 Bad Request response. + * + * @throws \Throwable + */ + public function pingApi(): void { + $endpoint = sprintf('%s/%s/%s/documents/%s', + $this->settings->getFasitApiBaseUrl(), + $this->settings->getFasitApiTenant(), + $this->settings->getFasitApiVersion(), + self::FASIT_API_METHOD_UPLOAD + ); + + try { + $this->post($endpoint, []); + } + catch (\Throwable $t) { + // Throw if it's not a 400 Bad Request exception. + if (!($t instanceof GuzzleException) + || Response::HTTP_BAD_REQUEST !== $t->getCode()) { + throw $t; + } + } + } + } diff --git a/modules/os2forms_fasit/src/Helper/Settings.php b/modules/os2forms_fasit/src/Helper/Settings.php index de065fc6..98d5a5e8 100644 --- a/modules/os2forms_fasit/src/Helper/Settings.php +++ b/modules/os2forms_fasit/src/Helper/Settings.php @@ -2,66 +2,72 @@ namespace Drupal\os2forms_fasit\Helper; -use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; -use Drupal\Core\KeyValueStore\KeyValueStoreInterface; -use Drupal\os2forms_fasit\Exception\InvalidSettingException; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; +use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_fasit\Form\SettingsForm; -use Symfony\Component\OptionsResolver\OptionsResolver; /** * General settings for os2forms_fasit. */ final class Settings { /** - * The store. + * The config. * - * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface + * @var \Drupal\Core\Config\ImmutableConfig */ - private KeyValueStoreInterface $store; + private ImmutableConfig $config; /** - * The key value collection name. - * - * @var string + * The constructor. */ - private $collection = 'os2forms_fasit'; + public function __construct( + ConfigFactoryInterface $configFactory, + private readonly KeyRepositoryInterface $keyRepository, + ) { + $this->config = $configFactory->get(SettingsForm::CONFIG_NAME); + } /** - * The constructor. + * Get fasit api base url. */ - public function __construct(KeyValueFactoryInterface $keyValueFactory) { - $this->store = $keyValueFactory->get($this->collection); + public function getFasitApiBaseUrl(): ?string { + return $this->get(SettingsForm::FASIT_API_BASE_URL); } /** - * Get fasit api base url. + * Get fasit api tenant. */ - public function getFasitApiBaseUrl(): string { - return $this->get(SettingsForm::FASIT_API_BASE_URL, ''); + public function getFasitApiTenant(): ?string { + return $this->get(SettingsForm::FASIT_API_TENANT); } /** - * Get fasit api base url. + * Get fasit api version. */ - public function getFasitApiTenant(): string { - return $this->get(SettingsForm::FASIT_API_TENANT, ''); + public function getFasitApiVersion(): ?string { + return $this->get(SettingsForm::FASIT_API_VERSION); } + + + /** - * Get fasit api base url. + * Get key. */ - public function getFasitApiVersion(): string { - return $this->get(SettingsForm::FASIT_API_VERSION, ''); + public function getKey(): ?string { + return $this->get(SettingsForm::KEY); } /** * Get certificate. - * - * @phpstan-return array */ - public function getCertificate(): array { - $value = $this->get(SettingsForm::CERTIFICATE); - return is_array($value) ? $value : []; + public function getCertificate(): ?string { + $key = $this->keyRepository->getKey( + $this->getKey(), + ); + + return $key?->getKeyValue(); } /** @@ -75,42 +81,8 @@ public function getCertificate(): array { * @return mixed * The setting value. */ - private function get(string $key, $default = NULL) { - $resolver = $this->getSettingsResolver(); - if (!$resolver->isDefined($key)) { - throw new InvalidSettingException(sprintf('Setting %s is not defined', $key)); - } - - return $this->store->get($key, $default); - } - - /** - * Set settings. - * - * @throws \Symfony\Component\OptionsResolver\Exception\ExceptionInterface - * - * @phpstan-param array $settings - */ - public function setSettings(array $settings): self { - $settings = $this->getSettingsResolver()->resolve($settings); - foreach ($settings as $key => $value) { - $this->store->set($key, $value); - } - - return $this; - } - - /** - * Get settings resolver. - */ - private function getSettingsResolver(): OptionsResolver { - return (new OptionsResolver()) - ->setDefaults([ - SettingsForm::FASIT_API_BASE_URL => '', - SettingsForm::FASIT_API_TENANT => '', - SettingsForm::FASIT_API_VERSION => '', - SettingsForm::CERTIFICATE => [], - ]); + private function get(string $key, $default = NULL): mixed { + return $this->config->get($key) ?? $default; } } From d566ee0b113dea4e4e91b53d1a69fc3aa0957947 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 17 Jan 2025 14:57:15 +0100 Subject: [PATCH 21/88] Service update --- .../os2forms_digital_post/os2forms_digital_post.services.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 31ff12ef..9b096fad 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -32,7 +32,7 @@ services: Drupal\os2forms_digital_post\Helper\DigitalPostHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@os2web_key.key_helper" + - "@Drupal\\os2web_key\\KeyHelper" - "@plugin.manager.os2web_datalookup" - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" From 8d831d3ce146daa7956ddc99aba3ac489892f923 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 20 Jan 2025 10:02:15 +0100 Subject: [PATCH 22/88] Coding standards --- .../src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php | 3 +-- modules/os2forms_fasit/src/Helper/Settings.php | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php index 9d3b1aaa..0e50ffaf 100644 --- a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php +++ b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php @@ -6,7 +6,6 @@ use Drupal\Core\File\FileSystem; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\key\KeyRepository; use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_dawa\Entity\DatafordelerMatrikula; use Drupal\os2web_audit\Service\Logger; @@ -43,7 +42,7 @@ public function __construct( * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - /** @var Logger $auditLogger */ + /** @var \Drupal\os2web_audit\Service\Logger $auditLogger */ $auditLogger = $container->get('os2web_audit.logger'); /** @var \Drupal\key\KeyRepositoryInterface $keyRepository */ $keyRepository = $container->get('key.repository'); diff --git a/modules/os2forms_fasit/src/Helper/Settings.php b/modules/os2forms_fasit/src/Helper/Settings.php index 98d5a5e8..491175a0 100644 --- a/modules/os2forms_fasit/src/Helper/Settings.php +++ b/modules/os2forms_fasit/src/Helper/Settings.php @@ -48,10 +48,7 @@ public function getFasitApiTenant(): ?string { public function getFasitApiVersion(): ?string { return $this->get(SettingsForm::FASIT_API_VERSION); } - - - - + /** * Get key. */ From 1820f120c1b28ac1401cda231b9b1d65ea68ce9e Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 20 Jan 2025 10:09:19 +0100 Subject: [PATCH 23/88] Coding standards --- modules/os2forms_fasit/src/Helper/Settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_fasit/src/Helper/Settings.php b/modules/os2forms_fasit/src/Helper/Settings.php index 491175a0..ee9f7809 100644 --- a/modules/os2forms_fasit/src/Helper/Settings.php +++ b/modules/os2forms_fasit/src/Helper/Settings.php @@ -48,7 +48,7 @@ public function getFasitApiTenant(): ?string { public function getFasitApiVersion(): ?string { return $this->get(SettingsForm::FASIT_API_VERSION); } - + /** * Get key. */ From 34ec7140fb5208520da9d0d63cc00ef46782766a Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 21 Jan 2025 12:54:47 +0100 Subject: [PATCH 24/88] All methods for configuring certificate in fasit module works --- .../src/Helper/CertificateLocatorHelper.php | 0 .../os2forms_fasit.services.yml | 5 + .../os2forms_fasit/src/Form/SettingsForm.php | 129 +++++++++++++++++- .../src/Helper/CertificateLocatorHelper.php | 9 +- .../os2forms_fasit/src/Helper/FasitHelper.php | 70 +++++++++- .../os2forms_fasit/src/Helper/Settings.php | 33 ++++- 6 files changed, 228 insertions(+), 18 deletions(-) create mode 100644 modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php diff --git a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php new file mode 100644 index 00000000..e69de29b diff --git a/modules/os2forms_fasit/os2forms_fasit.services.yml b/modules/os2forms_fasit/os2forms_fasit.services.yml index e6fe5e8c..2bd6917f 100644 --- a/modules/os2forms_fasit/os2forms_fasit.services.yml +++ b/modules/os2forms_fasit/os2forms_fasit.services.yml @@ -4,10 +4,15 @@ services: - "@config.factory" - "@key.repository" + Drupal\os2forms_fasit\Helper\CertificateLocatorHelper: + arguments: + - "@Drupal\\os2forms_fasit\\Helper\\Settings" + Drupal\os2forms_fasit\Helper\FasitHelper: arguments: - '@http_client' - '@entity_type.manager' - "@Drupal\\os2forms_fasit\\Helper\\Settings" - "@file_system" + - "@Drupal\\os2forms_fasit\\Helper\\CertificateLocatorHelper" - "@os2web_audit.logger" diff --git a/modules/os2forms_fasit/src/Form/SettingsForm.php b/modules/os2forms_fasit/src/Form/SettingsForm.php index f703255c..53d461fd 100644 --- a/modules/os2forms_fasit/src/Form/SettingsForm.php +++ b/modules/os2forms_fasit/src/Form/SettingsForm.php @@ -6,6 +6,7 @@ use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\os2forms_fasit\Helper\CertificateLocatorHelper; use Drupal\os2forms_fasit\Helper\FasitHelper; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -21,6 +22,9 @@ final class SettingsForm extends ConfigFormBase { public const FASIT_API_VERSION = 'fasit_api_version'; public const CERTIFICATE = 'certificate'; public const KEY = 'key'; + public const CERTIFICATE_PROVIDER = 'certificate_provider'; + public const PROVIDER_TYPE_FORM = 'form'; + public const PROVIDER_TYPE_KEY = 'key'; public const ACTION_PING_API = 'action_ping_api'; @@ -98,14 +102,122 @@ public function buildForm(array $form, FormStateInterface $form_state): array { '#description' => $this->t('Specifies which api version to use. Should probably be v2'), ]; - $form[self::KEY] = [ + $certificateConfig = $config->get(self::CERTIFICATE) ?? []; + + $form[self::CERTIFICATE] = [ + '#type' => 'fieldset', + '#title' => $this->t('Certificate'), + '#tree' => TRUE, + + self::CERTIFICATE_PROVIDER => [ + '#type' => 'select', + '#title' => $this->t('Provider'), + '#options' => [ + self::PROVIDER_TYPE_FORM => $this->t('Form'), + self::PROVIDER_TYPE_KEY => $this->t('Key'), + ], + '#default_value' => $certificateConfig[self::CERTIFICATE_PROVIDER] ?? self::PROVIDER_TYPE_FORM, + '#description' => $this->t('Specifies which provider to use'), + ], + ]; + + $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE] = [ + '#type' => 'select', + '#title' => $this->t('Certificate locator type'), + '#options' => [ + CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT => $this->t('Azure key vault'), + CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM => $this->t('File system'), + ], + '#default_value' => $certificateConfig[CertificateLocatorHelper::LOCATOR_TYPE] ?? NULL, + '#states' => [ + 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM]], + ], + '#description' => $this->t('Specifies which locator to use'), + ]; + + $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT] = [ + '#type' => 'fieldset', + '#title' => $this->t('Azure key vault'), + '#states' => [ + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + 'and', + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT], + ], + ], + ]; + + $settings = [ + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_TENANT_ID => ['title' => $this->t('Tenant id')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_APPLICATION_ID => ['title' => $this->t('Application id')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_CLIENT_SECRET => ['title' => $this->t('Client secret')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_NAME => ['title' => $this->t('Name')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_SECRET => ['title' => $this->t('Secret')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_VERSION => ['title' => $this->t('Version')], + ]; + + foreach ($settings as $key => $info) { + $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] = [ + '#type' => 'textfield', + '#title' => $info['title'], + '#default_value' => $certificateConfig[CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] ?? NULL, + '#states' => [ + 'required' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + 'and', + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT], + ], + ], + ]; + } + + $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM] = [ + '#type' => 'fieldset', + '#title' => $this->t('File system'), + '#states' => [ + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + 'and', + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM], + ], + ], + + 'path' => [ + '#type' => 'textfield', + '#title' => $this->t('Path'), + '#default_value' => $certificateConfig[CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]['path'] ?? NULL, + '#states' => [ + 'required' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + 'and', + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM] + ], + ], + ], + ]; + + $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_PASSPHRASE] = [ + '#type' => 'textfield', + '#title' => $this->t('Passphrase'), + '#default_value' => $certificateConfig[CertificateLocatorHelper::LOCATOR_PASSPHRASE] ?? NULL, + '#states' => [ + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + ], + ], + ]; + + $form[self::CERTIFICATE][self::PROVIDER_TYPE_KEY] = [ '#type' => 'key_select', '#key_filters' => [ 'type' => 'os2web_key_certificate', ], '#title' => $this->t('Key'), '#required' => TRUE, - '#default_value' => $config->get(self::KEY), + '#default_value' => $config->get(self::PROVIDER_TYPE_KEY), + '#states' => [ + 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_KEY]], + ], ]; $form['actions']['ping_api'] = [ @@ -135,6 +247,17 @@ public function validateForm(array &$form, FormStateInterface $form_state): void return; } + $values = $form_state->getValues(); + + if (self::PROVIDER_TYPE_FORM === $values[self::CERTIFICATE][self::CERTIFICATE_PROVIDER]) { + if (CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM === $values[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE]) { + $path = $values[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]['path'] ?? NULL; + if (!file_exists($path)) { + $form_state->setErrorByName('certificate][file_system][path', $this->t('Invalid certificate path: %path', ['%path' => $path])); + } + } + } + parent::validateForm($form, $form_state); } @@ -160,7 +283,7 @@ public function submitForm(array &$form, FormStateInterface $form_state): void { self::FASIT_API_BASE_URL, self::FASIT_API_TENANT, self::FASIT_API_VERSION, - self::KEY, + self::CERTIFICATE, ] as $key) { $config->set($key, $form_state->getValue($key)); } diff --git a/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php index bca6e3d1..4d56a580 100644 --- a/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php +++ b/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php @@ -18,7 +18,6 @@ class CertificateLocatorHelper { public const LOCATOR_TYPE = 'locator_type'; public const LOCATOR_TYPE_AZURE_KEY_VAULT = 'azure_key_vault'; - public const LOCATOR_TYPE_HASHICORP_KEY_VAULT = 'hashicorp_key_vault'; public const LOCATOR_TYPE_FILE_SYSTEM = 'file_system'; public const LOCATOR_PASSPHRASE = 'passphrase'; public const LOCATOR_AZURE_KEY_VAULT_TENANT_ID = 'tenant_id'; @@ -39,12 +38,12 @@ public function __construct(private readonly Settings $settings) { * Get certificate locator. */ public function getCertificateLocator(): CertificateLocatorInterface { - $certificateSettings = $this->settings->getCertificate(); + $config = $this->settings->getFasitCertificateConfig(); - $locatorType = $certificateSettings[self::LOCATOR_TYPE]; - $options = $certificateSettings[$locatorType]; + $locatorType = $config[self::LOCATOR_TYPE]; + $options = $config[$locatorType]; $options += [ - self::LOCATOR_PASSPHRASE => $certificateSettings[self::LOCATOR_PASSPHRASE] ?: '', + self::LOCATOR_PASSPHRASE => $config[self::LOCATOR_PASSPHRASE] ?: '', ]; if (self::LOCATOR_TYPE_AZURE_KEY_VAULT === $locatorType) { diff --git a/modules/os2forms_fasit/src/Helper/FasitHelper.php b/modules/os2forms_fasit/src/Helper/FasitHelper.php index 25a3a4db..e3f8dc83 100644 --- a/modules/os2forms_fasit/src/Helper/FasitHelper.php +++ b/modules/os2forms_fasit/src/Helper/FasitHelper.php @@ -11,6 +11,7 @@ use Drupal\os2forms_fasit\Exception\FileTypeException; use Drupal\os2forms_fasit\Exception\InvalidSettingException; use Drupal\os2forms_fasit\Exception\InvalidSubmissionException; +use Drupal\os2forms_fasit\Form\SettingsForm; use Drupal\os2forms_fasit\Plugin\WebformHandler\FasitWebformHandler; use Drupal\os2web_audit\Service\Logger; use Drupal\webform\Entity\WebformSubmission; @@ -41,6 +42,7 @@ public function __construct( private readonly EntityTypeManagerInterface $entityTypeManager, private readonly Settings $settings, private readonly FileSystemInterface $fileSystem, + private readonly CertificateLocatorHelper $certificateLocator, private readonly Logger $auditLogger, ) { } @@ -219,7 +221,8 @@ private function uploadDocument(array $uploads, string $submissionId, array $han 'Content-Type' => 'application/xml', ], 'body' => $body, - 'cert' => $this->settings->getCertificate(), + // 'cert' => $this->settings->getKeyValue(), +// 'cert' => $this->getCertificate(), ]; // Attempt upload. @@ -494,11 +497,27 @@ private function getSubmission(string $submissionId): EntityInterface { */ private function post(string $endpoint, array $options): ResponseInterface { try { - $certificate = $this->settings->getCertificate(); - $certPath = $this->fileSystem->tempnam($this->fileSystem->getTempDirectory(), 'os2forms_fasit_cert'); - // `tempnam` has created a file, so we must replace when saving. - $this->fileSystem->saveData($certificate, $certPath, FileSystemInterface::EXISTS_REPLACE); - $options['cert'] = $certPath; + $config = $this->settings->getFasitCertificateConfig(); + + // Key => string + // Azure => file without passphrase + // Filesystem => file with potential passphrase. + $provider = $config['certificate_provider']; + + if (SettingsForm::PROVIDER_TYPE_KEY === $provider) { + $certificate = $this->settings->getKeyValue(); + $certPath = $this->fileSystem->tempnam($this->fileSystem->getTempDirectory(), 'os2forms_fasit_cert'); + // `tempnam` has created a file, so we must replace when saving. + $this->fileSystem->saveData($certificate, $certPath, FileSystemInterface::EXISTS_REPLACE); + $options['cert'] = $certPath; + } + elseif (SettingsForm::PROVIDER_TYPE_FORM === $provider) { + [$certificateOptions] = $this->getCertificateOptionsAndTempCertFilename(); + $options['cert'] = $certificateOptions; + } + else { + throw new InvalidSettingException('Invalid certificate configuration'); + } return $this->client->request('POST', $endpoint, $options); } finally { @@ -534,4 +553,43 @@ public function pingApi(): void { } } + /** + * Get certificate. + * + * @throws InvalidSettingException + */ + private function getCertificate(): mixed { + $config = $this->settings->getFasitCertificateConfig(); + $provider = $config['certificate_provider']; + if (SettingsForm::PROVIDER_TYPE_KEY === $provider) { + return $this->settings->getKeyValue(); + } + elseif (SettingsForm::PROVIDER_TYPE_FORM === $provider) { + [$certificateOptions, $tempCertFilename] = $this->getCertificateOptionsAndTempCertFilename(); + return $certificateOptions; + } + + throw new InvalidSettingException('Invalid certificate configuration'); + } + + /** + * Gets certificate options and temp certificate filename. + * + * @throws \Drupal\os2forms_fasit\Exception\CertificateLocatorException + * Certificate locator exception. + * + * @phpstan-return array + */ + private function getCertificateOptionsAndTempCertFilename(): array { + $certificateLocator = $this->certificateLocator->getCertificateLocator(); + $localCertFilename = tempnam(sys_get_temp_dir(), 'cert'); + file_put_contents($localCertFilename, $certificateLocator->getCertificate()); + $certificateOptions = + $certificateLocator->hasPassphrase() ? + [$localCertFilename, $certificateLocator->getPassphrase()] + : $localCertFilename; + + return [$certificateOptions, $localCertFilename]; + } + } diff --git a/modules/os2forms_fasit/src/Helper/Settings.php b/modules/os2forms_fasit/src/Helper/Settings.php index ee9f7809..d1db9702 100644 --- a/modules/os2forms_fasit/src/Helper/Settings.php +++ b/modules/os2forms_fasit/src/Helper/Settings.php @@ -50,18 +50,43 @@ public function getFasitApiVersion(): ?string { } /** - * Get key. + * Get Fasit configuration selector */ - public function getKey(): ?string { + public function getFasitCertificateConfig(): ?array { + return $this->get(SettingsForm::CERTIFICATE); + } + + /** + * Get Fasit certificate provider. + */ + public function getFasitCertificateProvider(): string { + $config = $this->getFasitCertificateConfig(); + + return $config[SettingsForm::CERTIFICATE_PROVIDER] ?? SettingsForm::PROVIDER_TYPE_FORM; + } + + /** + * Get Fasit certificate locator. + */ + public function getFasitCertificateLocator(): string { + $config = $this->getFasitCertificateConfig(); + + return $config[CertificateLocatorHelper::LOCATOR_TYPE] ?? CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM; + } + + /** + * Get Fasit key certificate configuration. + */ + public function getFasitCertificateKey(): ?string { return $this->get(SettingsForm::KEY); } /** * Get certificate. */ - public function getCertificate(): ?string { + public function getKeyValue(): ?string { $key = $this->keyRepository->getKey( - $this->getKey(), + $this->getFasitCertificateKey(), ); return $key?->getKeyValue(); From 33d0c3b0ce5301a77e08dc4e918588f5d3117127 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 21 Jan 2025 13:19:22 +0100 Subject: [PATCH 25/88] Applied coding standards --- .../os2forms_fasit/src/Form/SettingsForm.php | 6 +----- .../os2forms_fasit/src/Helper/FasitHelper.php | 21 ------------------- .../os2forms_fasit/src/Helper/Settings.php | 2 +- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/modules/os2forms_fasit/src/Form/SettingsForm.php b/modules/os2forms_fasit/src/Form/SettingsForm.php index 53d461fd..fdd6e0e0 100644 --- a/modules/os2forms_fasit/src/Form/SettingsForm.php +++ b/modules/os2forms_fasit/src/Form/SettingsForm.php @@ -141,7 +141,6 @@ public function buildForm(array $form, FormStateInterface $form_state): array { '#states' => [ 'visible' => [ ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], - 'and', ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT], ], ], @@ -164,7 +163,6 @@ public function buildForm(array $form, FormStateInterface $form_state): array { '#states' => [ 'required' => [ ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], - 'and', ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT], ], ], @@ -177,7 +175,6 @@ public function buildForm(array $form, FormStateInterface $form_state): array { '#states' => [ 'visible' => [ ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], - 'and', ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM], ], ], @@ -189,8 +186,7 @@ public function buildForm(array $form, FormStateInterface $form_state): array { '#states' => [ 'required' => [ ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], - 'and', - ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM] + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM], ], ], ], diff --git a/modules/os2forms_fasit/src/Helper/FasitHelper.php b/modules/os2forms_fasit/src/Helper/FasitHelper.php index e3f8dc83..12a5c51f 100644 --- a/modules/os2forms_fasit/src/Helper/FasitHelper.php +++ b/modules/os2forms_fasit/src/Helper/FasitHelper.php @@ -221,8 +221,6 @@ private function uploadDocument(array $uploads, string $submissionId, array $han 'Content-Type' => 'application/xml', ], 'body' => $body, - // 'cert' => $this->settings->getKeyValue(), -// 'cert' => $this->getCertificate(), ]; // Attempt upload. @@ -553,25 +551,6 @@ public function pingApi(): void { } } - /** - * Get certificate. - * - * @throws InvalidSettingException - */ - private function getCertificate(): mixed { - $config = $this->settings->getFasitCertificateConfig(); - $provider = $config['certificate_provider']; - if (SettingsForm::PROVIDER_TYPE_KEY === $provider) { - return $this->settings->getKeyValue(); - } - elseif (SettingsForm::PROVIDER_TYPE_FORM === $provider) { - [$certificateOptions, $tempCertFilename] = $this->getCertificateOptionsAndTempCertFilename(); - return $certificateOptions; - } - - throw new InvalidSettingException('Invalid certificate configuration'); - } - /** * Gets certificate options and temp certificate filename. * diff --git a/modules/os2forms_fasit/src/Helper/Settings.php b/modules/os2forms_fasit/src/Helper/Settings.php index d1db9702..09d0cd92 100644 --- a/modules/os2forms_fasit/src/Helper/Settings.php +++ b/modules/os2forms_fasit/src/Helper/Settings.php @@ -50,7 +50,7 @@ public function getFasitApiVersion(): ?string { } /** - * Get Fasit configuration selector + * Get Fasit configuration selector. */ public function getFasitCertificateConfig(): ?array { return $this->get(SettingsForm::CERTIFICATE); From d5d476b6be6fde48cb9e8fa33cbbd2f0b99d923c Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 21 Jan 2025 13:54:58 +0100 Subject: [PATCH 26/88] Updated fasit test command --- modules/os2forms_fasit/drush.services.yml | 3 ++- .../src/{Drush => }/Commands/FasitTestCommands.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename modules/os2forms_fasit/src/{Drush => }/Commands/FasitTestCommands.php (93%) diff --git a/modules/os2forms_fasit/drush.services.yml b/modules/os2forms_fasit/drush.services.yml index 7b8cbec9..462221c7 100644 --- a/modules/os2forms_fasit/drush.services.yml +++ b/modules/os2forms_fasit/drush.services.yml @@ -1,5 +1,6 @@ services: - Drupal\os2forms_fasit\Drush\Commands\FasitTestCommands: + os2forms_fasit.commands: + class: \Drupal\os2forms_fasit\Commands\FasitTestCommands arguments: - '@Drupal\os2forms_fasit\Helper\FasitHelper' tags: diff --git a/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommands.php b/modules/os2forms_fasit/src/Commands/FasitTestCommands.php similarity index 93% rename from modules/os2forms_fasit/src/Drush/Commands/FasitTestCommands.php rename to modules/os2forms_fasit/src/Commands/FasitTestCommands.php index 4ac71534..f24536a9 100644 --- a/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommands.php +++ b/modules/os2forms_fasit/src/Commands/FasitTestCommands.php @@ -1,6 +1,6 @@ Date: Wed, 22 Jan 2025 10:40:48 +0100 Subject: [PATCH 27/88] Updated Fasit ping command --- modules/os2forms_fasit/drush.services.yml | 7 ------- .../Commands/FasitTestCommand.php} | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 11 deletions(-) delete mode 100644 modules/os2forms_fasit/drush.services.yml rename modules/os2forms_fasit/src/{Commands/FasitTestCommands.php => Drush/Commands/FasitTestCommand.php} (56%) diff --git a/modules/os2forms_fasit/drush.services.yml b/modules/os2forms_fasit/drush.services.yml deleted file mode 100644 index 462221c7..00000000 --- a/modules/os2forms_fasit/drush.services.yml +++ /dev/null @@ -1,7 +0,0 @@ -services: - os2forms_fasit.commands: - class: \Drupal\os2forms_fasit\Commands\FasitTestCommands - arguments: - - '@Drupal\os2forms_fasit\Helper\FasitHelper' - tags: - - { name: drush.command } diff --git a/modules/os2forms_fasit/src/Commands/FasitTestCommands.php b/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php similarity index 56% rename from modules/os2forms_fasit/src/Commands/FasitTestCommands.php rename to modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php index f24536a9..586adc04 100644 --- a/modules/os2forms_fasit/src/Commands/FasitTestCommands.php +++ b/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php @@ -1,21 +1,32 @@ get(FasitHelper::class), + ); } /** From fa892f9746d714a6f73099684fb9e86d5180ef69 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 22 Jan 2025 11:41:49 +0100 Subject: [PATCH 28/88] Fasit module, cleaned up services --- CHANGELOG.md | 2 ++ .../os2forms_fasit/os2forms_fasit.services.yml | 15 +++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2feae1a1..9ba06141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa - [#101](https://github.com/OS2Forms/os2forms/pull/101) - Added support for `os2web_key` in Digital post - Added support for `os2web_key` in Fasit handler. + - Switched from saving settings in key value store to config, i.e + the module needs to be reconfigured. ## [3.21.1] 2025-01-06 diff --git a/modules/os2forms_fasit/os2forms_fasit.services.yml b/modules/os2forms_fasit/os2forms_fasit.services.yml index 2bd6917f..e70008fb 100644 --- a/modules/os2forms_fasit/os2forms_fasit.services.yml +++ b/modules/os2forms_fasit/os2forms_fasit.services.yml @@ -1,18 +1,13 @@ services: Drupal\os2forms_fasit\Helper\Settings: + autowire: true arguments: - - "@config.factory" - - "@key.repository" + $keyRepository: "@key.repository" Drupal\os2forms_fasit\Helper\CertificateLocatorHelper: - arguments: - - "@Drupal\\os2forms_fasit\\Helper\\Settings" + autowire: true Drupal\os2forms_fasit\Helper\FasitHelper: + autowire: true arguments: - - '@http_client' - - '@entity_type.manager' - - "@Drupal\\os2forms_fasit\\Helper\\Settings" - - "@file_system" - - "@Drupal\\os2forms_fasit\\Helper\\CertificateLocatorHelper" - - "@os2web_audit.logger" + $auditLogger: "@os2web_audit.logger" From 6f0d1c2a89c2d38acc953bc5dd528c27ed771f97 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 7 May 2024 14:26:01 +0200 Subject: [PATCH 29/88] Moved digital post settings into config. Added support for os2web_key to get certificate. --- .github/workflows/pr.yml | 31 +-- .gitignore | 2 - .markdownlint.jsonc | 13 + .markdownlintrc | 18 -- CHANGELOG.md | 2 + README.md | 20 +- composer.json | 68 +++-- modules/os2forms_digital_post/README.md | 4 + .../os2forms_digital_post.info.yml | 1 + .../os2forms_digital_post.install | 9 + .../os2forms_digital_post.services.yml | 6 +- .../src/Exception/InvalidSettingException.php | 10 - .../src/Form/SettingsForm.php | 238 +++++++----------- .../src/Helper/CertificateLocatorHelper.php | 79 ------ .../src/Helper/DigitalPostHelper.php | 8 +- .../src/Helper/KeyCertificateLocator.php | 59 +++++ .../src/Helper/MemoryCertificateLocator.php | 49 ++++ .../src/Helper/Settings.php | 146 +++++++---- package.json | 13 - scripts/code-analysis | 18 +- 20 files changed, 417 insertions(+), 377 deletions(-) create mode 100644 .markdownlint.jsonc delete mode 100644 .markdownlintrc delete mode 100644 modules/os2forms_digital_post/src/Exception/InvalidSettingException.php delete mode 100644 modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php create mode 100644 modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php create mode 100644 modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php delete mode 100644 package.json diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f50b23dc..c78e9623 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -50,6 +50,9 @@ jobs: composer validate --strict composer.json # Check that dependencies resolve. composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + - name: Check that composer file is normalized + run: | + composer normalize --dry-run php-coding-standards: name: PHP coding standards @@ -113,27 +116,13 @@ jobs: run: | ./scripts/code-analysis - markdownlint: + coding-standards-markdown: + name: Markdown coding standards 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 + uses: actions/checkout@master + + - name: Coding standards + run: | + docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' diff --git a/.gitignore b/.gitignore index 1cc8643c..1a6c2523 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,3 @@ composer.lock vendor -node_modules/ -yarn.lock diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 00000000..a28c5809 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,13 @@ +{ + "default": true, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md + "line-length": { + "line_length": 120, + "code_blocks": false, + "tables": false + }, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md + "no-duplicate-heading": { + "siblings_only": true + } +} diff --git a/.markdownlintrc b/.markdownlintrc deleted file mode 100644 index 75637156..00000000 --- a/.markdownlintrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - // @see https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.jsonc - // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md - "MD013": { - // Exclude code blocks - "code_blocks": false - }, - - // Prevent complaining on duplicated headings in CHANGELOG.md - // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md - "MD024": { - "siblings_only": true - } -} - -// Local Variables: -// mode: json -// End: diff --git a/CHANGELOG.md b/CHANGELOG.md index 274eb0cc..23821caf 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] +- [#101](https://github.com/OS2Forms/os2forms/pull/101) + Added support for os2web_key - Removed modules ldap_auth, logging_alerts, maillog ## [3.21.2] 2025-01-07 diff --git a/README.md b/README.md index 12e4ad05..90245a21 100644 --- a/README.md +++ b/README.md @@ -122,29 +122,27 @@ run the checks locally. ```sh docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer install -docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer coding-standards-check - # Fix (some) coding standards issues. docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer coding-standards-apply +docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer coding-standards-check ``` ### Markdown ```sh -docker run --rm --volume ${PWD}:/app --workdir /app node:20 yarn install -docker run --rm --volume ${PWD}:/app --workdir /app node:20 yarn coding-standards-check/markdownlint - -# Fix (some) coding standards issues. -docker run --rm --volume ${PWD}:/app --workdir /app node:20 yarn coding-standards-apply/markdownlint +docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' --fix +docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' ``` ## Code analysis We use [PHPStan](https://phpstan.org/) for static code analysis. -Running statis code analysis on a standalone Drupal module is a bit tricky, so -we use a helper script to run the analysis: +Running statis code analysis on a standalone Drupal module is a bit tricky, so we use a helper script to run the +analysis: -```sh -./scripts/code-analysis +```shell +docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm ./scripts/code-analysis ``` + +**Note**: Currently the code analysis is only run on the `os2forms_digital_post` sub-module (cf. [`phpstan.neon`](./phpstan.neon)). diff --git a/composer.json b/composer.json index 828bd6ca..f997c480 100644 --- a/composer.json +++ b/composer.json @@ -1,20 +1,8 @@ { "name": "os2forms/os2forms", - "type": "drupal-module", "description": "Drupal 8 OS2Form module provides advanced webform functionality 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" - } - }, + "type": "drupal-module", "require": { "php": "^8.1", "ext-dom": "*", @@ -43,7 +31,7 @@ "drupal/mailsystem": "^4.1", "drupal/masquerade": "^2.0@RC", "drupal/pathauto": "^1.5", - "drupal/permissions_by_term": "^3.1 || ^2.25", + "drupal/permissions_by_term": "^2.25 || ^3.1", "drupal/queue_mail": "^1.4", "drupal/r4032login": "^2.1", "drupal/redirect": "^1.4", @@ -71,6 +59,7 @@ "itk-dev/serviceplatformen": "^1.5", "os2web/os2web_audit": "^0.1.6", "os2web/os2web_datalookup": "^2.0", + "os2web/os2web_key": "dev-os2web_key", "os2web/os2web_nemlogin": "^1.0", "os2web/os2web_simplesaml": "dev-master", "php-http/guzzle7-adapter": "^1.0", @@ -84,15 +73,44 @@ "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", "drupal/coder": "^8.3", + "ergebnis/composer-normalize": "^2.42", "mglaman/phpstan-drupal": "^1.1", "phpstan/extension-installer": "^1.3", "phpstan/phpstan-deprecation-rules": "^1.1", "phpunit/phpunit": "^9.5", "wsdltophp/packagegenerator": "^4.0" }, - "extra" : { + "repositories": { + "os2web/os2web_key": { + "type": "vcs", + "url": "https://github.com/itk-dev/os2web_key" + }, + "drupal": { + "type": "composer", + "url": "https://packages.drupal.org/8" + }, + "assets": { + "type": "composer", + "url": "https://asset-packagist.org" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "allow-plugins": { + "cweagans/composer-patches": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "ergebnis/composer-normalize": true, + "phpstan/extension-installer": true, + "simplesamlphp/composer-module-installer": true, + "vaimo/composer-patches": true, + "zaporylie/composer-drupal-optimizations": true + }, + "sort-packages": true + }, + "extra": { "composer-exit-on-patch-failure": false, - "enable-patching" : true, + "enable-patching": true, "patches": { "drupal/entity_print": { "2733781 - Add Export to Word Support": "https://www.drupal.org/files/issues/2019-11-22/2733781-47.patch" @@ -124,23 +142,23 @@ } }, "scripts": { - "code-analysis/phpstan": [ - "phpstan analyse" - ], "code-analysis": [ "@code-analysis/phpstan" ], - "coding-standards-check/phpcs": [ - "phpcs --standard=phpcs.xml.dist" + "code-analysis/phpstan": [ + "phpstan analyse" ], - "coding-standards-check": [ - "@coding-standards-check/phpcs" + "coding-standards-apply": [ + "@coding-standards-apply/phpcs" ], "coding-standards-apply/phpcs": [ "phpcbf --standard=phpcs.xml.dist" ], - "coding-standards-apply": [ - "@coding-standards-apply/phpcs" + "coding-standards-check": [ + "@coding-standards-check/phpcs" + ], + "coding-standards-check/phpcs": [ + "phpcs --standard=phpcs.xml.dist" ] }, "config": { diff --git a/modules/os2forms_digital_post/README.md b/modules/os2forms_digital_post/README.md index a487b13e..c36e9f48 100644 --- a/modules/os2forms_digital_post/README.md +++ b/modules/os2forms_digital_post/README.md @@ -31,6 +31,10 @@ examples](modules/os2forms_digital_post_examples/README.md). Go to `/admin/os2forms_digital_post/settings` to set up global settings for digital post. +### Key + +We use [os2web_key](https://github.com/OS2web/os2web_key) to provide the certificate for sending digital post. + ### Queue The actual sending of digital post is handled by jobs in an [Advanced diff --git a/modules/os2forms_digital_post/os2forms_digital_post.info.yml b/modules/os2forms_digital_post/os2forms_digital_post.info.yml index 71a17688..0e1408cc 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.info.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.info.yml @@ -7,6 +7,7 @@ dependencies: - 'beskedfordeler:beskedfordeler' - 'drupal:advancedqueue' - 'os2web_datalookup:os2web_datalookup' + - 'os2web_key:os2web_key' - 'webform:webform' - 'webform:webform_submission_log' - 'os2web:os2web_audit' diff --git a/modules/os2forms_digital_post/os2forms_digital_post.install b/modules/os2forms_digital_post/os2forms_digital_post.install index 760743cb..80b756ac 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.install +++ b/modules/os2forms_digital_post/os2forms_digital_post.install @@ -17,3 +17,12 @@ use Drupal\os2forms_digital_post\Helper\BeskedfordelerHelper; function os2forms_digital_post_schema() { return Drupal::service(BeskedfordelerHelper::class)->schema(); } + +/** + * Implements hook_update_N(). + */ +function os2forms_digital_post_update_9001() { + \Drupal::service('module_installer')->install([ + 'os2web_key', + ], TRUE); +} diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index c13fb96f..66bc3132 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -9,11 +9,13 @@ services: Drupal\os2forms_digital_post\Helper\Settings: arguments: - - "@keyvalue" + - "@config.factory" + - "@key.repository" Drupal\os2forms_digital_post\Helper\CertificateLocatorHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" + - "@key.repository" Drupal\os2forms_digital_post\Helper\MeMoHelper: arguments: @@ -30,7 +32,7 @@ services: Drupal\os2forms_digital_post\Helper\DigitalPostHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@Drupal\\os2forms_digital_post\\Helper\\CertificateLocatorHelper" + - "@Drupal\\os2web_key\\CertificateHelper" - "@plugin.manager.os2web_datalookup" - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" diff --git a/modules/os2forms_digital_post/src/Exception/InvalidSettingException.php b/modules/os2forms_digital_post/src/Exception/InvalidSettingException.php deleted file mode 100644 index c3d34af6..00000000 --- a/modules/os2forms_digital_post/src/Exception/InvalidSettingException.php +++ /dev/null @@ -1,10 +0,0 @@ -queueStorage = $entityTypeManager->getStorage('advancedqueue_queue'); } @@ -44,12 +46,23 @@ public function __construct( */ public static function create(ContainerInterface $container) { return new static( + $container->get('config.factory'), + $container->get('entity_type.manager'), $container->get(Settings::class), - $container->get(CertificateLocatorHelper::class), - $container->get('entity_type.manager') ); } + /** + * {@inheritdoc} + * + * @phpstan-return array + */ + protected function getEditableConfigNames() { + return [ + Settings::CONFIG_NAME, + ]; + } + /** * {@inheritdoc} */ @@ -63,15 +76,26 @@ public function getFormId() { * @phpstan-param array $form * @phpstan-return array */ - public function buildForm(array $form, FormStateInterface $form_state) { - $form['test_mode'] = [ + public function buildForm(array $form, FormStateInterface $form_state): array { + $form = parent::buildForm($form, $form_state); + + $form['message'] = [ + '#theme' => 'status_messages', + '#message_list' => [ + 'status' => [ + $this->t('Use drush os2forms-digital-post:test:send to test sending digital post.'), + ], + ], + ]; + + $form[Settings::TEST_MODE] = [ '#type' => 'checkbox', '#title' => $this->t('Test mode'), - '#default_value' => $this->settings->getTestMode(), + '#default_value' => $this->settings->getEditableValue(Settings::TEST_MODE), + '#description' => $this->createDescription(Settings::TEST_MODE), ]; - $sender = $this->settings->getSender(); - $form['sender'] = [ + $form[Settings::SENDER] = [ '#type' => 'fieldset', '#title' => $this->t('Sender'), '#tree' => TRUE, @@ -82,126 +106,74 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#options' => [ 'CVR' => $this->t('CVR'), ], - '#default_value' => $sender[Settings::SENDER_IDENTIFIER_TYPE] ?? 'CVR', + '#default_value' => $this->settings->getEditableValue([Settings::SENDER, Settings::SENDER_IDENTIFIER_TYPE]) ?? 'CVR', '#required' => TRUE, + '#description' => $this->createDescription([Settings::SENDER, Settings::SENDER_IDENTIFIER_TYPE]), ], Settings::SENDER_IDENTIFIER => [ '#type' => 'textfield', '#title' => $this->t('Identifier'), - '#default_value' => $sender[Settings::SENDER_IDENTIFIER] ?? NULL, + '#default_value' => $this->settings->getEditableValue([Settings::SENDER, Settings::SENDER_IDENTIFIER]), '#required' => TRUE, + '#description' => $this->createDescription([Settings::SENDER, Settings::SENDER_IDENTIFIER]), ], Settings::FORSENDELSES_TYPE_IDENTIFIKATOR => [ '#type' => 'textfield', '#title' => $this->t('Forsendelsestypeidentifikator'), - '#default_value' => $sender[Settings::FORSENDELSES_TYPE_IDENTIFIKATOR] ?? NULL, + '#default_value' => $this->settings->getEditableValue([ + Settings::SENDER, Settings::FORSENDELSES_TYPE_IDENTIFIKATOR, + ]), '#required' => TRUE, + '#description' => $this->createDescription([Settings::SENDER, Settings::FORSENDELSES_TYPE_IDENTIFIKATOR]), ], ]; - $certificate = $this->settings->getCertificate(); - $form['certificate'] = [ + $form[Settings::CERTIFICATE] = [ '#type' => 'fieldset', '#title' => $this->t('Certificate'), '#tree' => TRUE, - 'locator_type' => [ - '#type' => 'select', - '#title' => $this->t('Certificate locator type'), - '#options' => [ - 'azure_key_vault' => $this->t('Azure key vault'), - 'file_system' => $this->t('File system'), - ], - '#default_value' => $certificate['locator_type'] ?? NULL, - ], - ]; - - $form['certificate'][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT] = [ - '#type' => 'fieldset', - '#title' => $this->t('Azure key vault'), - '#states' => [ - 'visible' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT]], - ], - ]; - - $settings = [ - 'tenant_id' => ['title' => $this->t('Tenant id')], - 'application_id' => ['title' => $this->t('Application id')], - 'client_secret' => ['title' => $this->t('Client secret')], - 'name' => ['title' => $this->t('Name')], - 'secret' => ['title' => $this->t('Secret')], - 'version' => ['title' => $this->t('Version')], - ]; - - foreach ($settings as $key => $info) { - $form['certificate'][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] = [ - '#type' => 'textfield', - '#title' => $info['title'], - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] ?? NULL, - '#states' => [ - 'required' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT]], - ], - ]; - } - - $form['certificate'][CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM] = [ - '#type' => 'fieldset', - '#title' => $this->t('File system'), - '#states' => [ - 'visible' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]], - ], - - 'path' => [ - '#type' => 'textfield', - '#title' => $this->t('Path'), - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]['path'] ?? NULL, - '#states' => [ - 'required' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]], + Settings::KEY => [ + '#type' => 'key_select', + '#key_filters' => [ + 'type' => 'os2web_key_certificate', ], + '#key_description' => FALSE, + '#title' => $this->t('Key'), + '#default_value' => $this->settings->getEditableValue([Settings::CERTIFICATE, Settings::KEY]), + '#required' => TRUE, + '#description' => $this->createDescription([Settings::CERTIFICATE, Settings::KEY]), ], ]; - $form['certificate']['passphrase'] = [ - '#type' => 'textfield', - '#title' => $this->t('Passphrase'), - '#default_value' => $certificate['passphrase'] ?? NULL, - ]; - - $processing = $this->settings->getProcessing(); - $form['processing'] = [ + $form[Settings::PROCESSING] = [ '#type' => 'fieldset', '#title' => $this->t('Processing'), '#tree' => TRUE, ]; - $defaultValue = $processing['queue'] ?? 'os2forms_digital_post'; - $form['processing']['queue'] = [ + $queue = $this->settings->getEditableValue([Settings::PROCESSING, Settings::QUEUE]); + $form[Settings::PROCESSING][Settings::QUEUE] = [ '#type' => 'select', '#title' => $this->t('Queue'), '#options' => array_map( static fn(EntityInterface $queue) => $queue->label(), $this->queueStorage->loadMultiple() ), - '#default_value' => $defaultValue, - '#description' => $this->t("Queue for digital post jobs. The queue must be run via Drupal's cron or via drush advancedqueue:queue:process @queue(in a cron job).", [ - '@queue' => $defaultValue, - ':queue_url' => '/admin/config/system/queues/jobs/' . urlencode($defaultValue), - ]), - ]; - - $form['actions']['#type'] = 'actions'; - - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Save settings'), - ]; - - $form['actions']['testCertificate'] = [ - '#type' => 'submit', - '#name' => 'testCertificate', - '#value' => $this->t('Test certificate'), + '#required' => TRUE, + '#default_value' => $queue, + '#description' => $this->createDescription([Settings::PROCESSING, Settings::QUEUE], + $queue + ? $this->t("Queue for digital post jobs. The queue must be run via Drupal's cron or via drush advancedqueue:queue:process @queue (in a cron job).", [ + '@queue' => $queue, + ':queue_url' => Url::fromRoute('view.advancedqueue_jobs.page_1', [ + 'arg_0' => $queue, + ])->toString(TRUE)->getGeneratedUrl(), + ]) + : $this->t("Queue for digital post jobs. The queue must be processed via Drupal's cron or drush advancedqueue:queue:process (in a cron job)."), + ), ]; return $form; @@ -212,59 +184,41 @@ public function buildForm(array $form, FormStateInterface $form_state) { * * @phpstan-param array $form */ - public function validateForm(array &$form, FormStateInterface $formState): void { - $triggeringElement = $formState->getTriggeringElement(); - if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { - return; + public function submitForm(array &$form, FormStateInterface $form_state): void { + $config = $this->config(Settings::CONFIG_NAME); + foreach ([ + Settings::TEST_MODE, + Settings::SENDER, + Settings::CERTIFICATE, + Settings::PROCESSING, + ] as $key) { + $config->set($key, $form_state->getValue($key)); } + $config->save(); - $values = $formState->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])); - } - } + parent::submitForm($form, $form_state); } /** - * {@inheritdoc} + * Create form field description with information on any runtime override. * - * @phpstan-param array $form + * @param string|array $key + * The key. + * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $description + * The actual field description. + * + * @return string + * The full description. */ - public function submitForm(array &$form, FormStateInterface $formState): void { - $triggeringElement = $formState->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'); - $this->settings->setSettings($settings); - $this->messenger()->addStatus($this->t('Settings saved')); - } - catch (OptionsResolverException $exception) { - $this->messenger()->addError($this->t('Settings not saved (@message)', ['@message' => $exception->getMessage()])); + private function createDescription(string|array $key, ?TranslatableMarkup $description = NULL): string { + if ($value = $this->settings->getOverride($key)) { + if (!empty($description)) { + $description .= '
'; + } + $description .= $this->t('Note: overridden on runtime with the value @value.', ['@value' => var_export($value['runtime'], TRUE)]); } - } - /** - * Test certificate. - */ - private function testCertificate(): void { - try { - $certificateLocator = $this->certificateLocatorHelper->getCertificateLocator(); - $certificateLocator->getCertificates(); - $this->messenger()->addStatus($this->t('Certificate succesfully tested')); - } - catch (\Throwable $throwable) { - $message = $this->t('Error testing certificate: %message', ['%message' => $throwable->getMessage()]); - $this->messenger()->addError($message); - } + return (string) $description; } } diff --git a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php deleted file mode 100644 index 10e6ac57..00000000 --- a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php +++ /dev/null @@ -1,79 +0,0 @@ -settings->getCertificate(); - - $locatorType = $certificateSettings['locator_type']; - $options = $certificateSettings[$locatorType]; - $options += [ - 'passphrase' => $certificateSettings['passphrase'] ?: '', - ]; - - if (self::LOCATOR_TYPE_AZURE_KEY_VAULT === $locatorType) { - $httpClient = new GuzzleAdapter(new Client()); - $requestFactory = new RequestFactory(); - - $vaultToken = new VaultToken($httpClient, $requestFactory); - - $token = $vaultToken->getToken( - $options['tenant_id'], - $options['application_id'], - $options['client_secret'], - ); - - $vault = new VaultSecret( - $httpClient, - $requestFactory, - $options['name'], - $token->getAccessToken() - ); - - return new AzureKeyVaultCertificateLocator( - $vault, - $options['secret'], - $options['version'], - $options['passphrase'], - ); - } - elseif (self::LOCATOR_TYPE_FILE_SYSTEM === $locatorType) { - $certificatepath = realpath($options['path']) ?: NULL; - if (NULL === $certificatepath) { - throw new CertificateLocatorException(sprintf('Invalid certificate path %s', $options['path'])); - } - return new FilesystemCertificateLocator($certificatepath, $options['passphrase']); - } - - throw new CertificateLocatorException(sprintf('Invalid certificate locator type: %s', $locatorType)); - } - -} diff --git a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php index 8681cf35..fc2355af 100644 --- a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php +++ b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php @@ -11,6 +11,7 @@ use Drupal\os2web_datalookup\Plugin\DataLookupManager; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCompanyInterface; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCprInterface; +use Drupal\os2web_key\CertificateHelper; use Drupal\webform\WebformSubmissionInterface; use ItkDev\Serviceplatformen\Service\SF1601\SF1601; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; @@ -29,7 +30,7 @@ final class DigitalPostHelper implements LoggerInterface { */ public function __construct( private readonly Settings $settings, - private readonly CertificateLocatorHelper $certificateLocatorHelper, + private readonly CertificateHelper $certificateHelper, private readonly DataLookupManager $dataLookupManager, private readonly MeMoHelper $meMoHelper, private readonly ForsendelseHelper $forsendelseHelper, @@ -62,7 +63,10 @@ public function sendDigitalPost(string $type, Message $message, ?ForsendelseI $f $options = [ 'test_mode' => (bool) $this->settings->getTestMode(), 'authority_cvr' => $senderSettings[Settings::SENDER_IDENTIFIER], - 'certificate_locator' => $this->certificateLocatorHelper->getCertificateLocator(), + 'certificate_locator' => new KeyCertificateLocator( + $this->settings->getCertificateKey(), + $this->certificateHelper + ), ]; $service = new SF1601($options); $transactionId = Serializer::createUuid(); diff --git a/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php b/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php new file mode 100644 index 00000000..832bc19f --- /dev/null +++ b/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php @@ -0,0 +1,59 @@ + + */ + public function getCertificates(): array { + if (!isset($this->certificates)) { + $this->certificates = $this->certificateHelper->getCertificates($this->key); + } + + return $this->certificates; + } + + /** + * {@inheritdoc} + */ + public function getCertificate(): string { + return $this->key->getKeyValue(); + } + + /** + * {@inheritdoc} + */ + public function getAbsolutePathToCertificate(): string { + throw new CertificateLocatorException(__METHOD__ . ' should not be used.'); + } + +} diff --git a/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php b/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php new file mode 100644 index 00000000..69d791d2 --- /dev/null +++ b/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php @@ -0,0 +1,49 @@ + + */ + public function getCertificates(): array { + $certificates = []; + $this->passphrase = 'P5bISuw?s:u4'; + if (!openssl_pkcs12_read($this->certificate, $certificates, $this->passphrase)) { + throw new CertificateLocatorException(sprintf('Could not read certificate: %s', openssl_error_string() ?: '')); + } + + return $certificates; + } + + /** + * {@inheritdoc} + */ + public function getCertificate(): string { + return $this->certificate; + } + + /** + * {@inheritdoc} + */ + public function getAbsolutePathToCertificate(): string { + throw new CertificateLocatorException(__METHOD__ . ' should not be used.'); + } + +} diff --git a/modules/os2forms_digital_post/src/Helper/Settings.php b/modules/os2forms_digital_post/src/Helper/Settings.php index e64be738..c0d5384d 100644 --- a/modules/os2forms_digital_post/src/Helper/Settings.php +++ b/modules/os2forms_digital_post/src/Helper/Settings.php @@ -2,45 +2,61 @@ namespace Drupal\os2forms_digital_post\Helper; -use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; -use Drupal\Core\KeyValueStore\KeyValueStoreInterface; -use Drupal\os2forms_digital_post\Exception\InvalidSettingException; -use Symfony\Component\OptionsResolver\OptionsResolver; +use Drupal\Core\Config\Config; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; +use Drupal\key\KeyInterface; +use Drupal\key\KeyRepositoryInterface; /** * General settings for os2forms_digital_post. */ final class Settings { + public const CONFIG_NAME = 'os2forms_digital_post.settings'; + + public const TEST_MODE = 'test_mode'; + + public const SENDER = 'sender'; public const SENDER_IDENTIFIER_TYPE = 'sender_identifier_type'; public const SENDER_IDENTIFIER = 'sender_identifier'; public const FORSENDELSES_TYPE_IDENTIFIKATOR = 'forsendelses_type_identifikator'; + public const CERTIFICATE = 'certificate'; + public const KEY = 'key'; + + public const PROCESSING = 'processing'; + public const QUEUE = 'queue'; + /** - * The store. + * The runtime (immutable) config. * - * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface + * @var \Drupal\Core\Config\ImmutableConfig */ - private KeyValueStoreInterface $store; + private ImmutableConfig $runtimeConfig; /** - * The key prefix. + * The (mutable) config. * - * @var string + * @var \Drupal\Core\Config\Config */ - private $collection = 'os2forms_digital_post.'; + private Config $editableConfig; /** - * Constructor. + * The constructor. */ - public function __construct(KeyValueFactoryInterface $keyValueFactory) { - $this->store = $keyValueFactory->get($this->collection); + public function __construct( + ConfigFactoryInterface $configFactory, + private readonly KeyRepositoryInterface $keyRepository, + ) { + $this->runtimeConfig = $configFactory->get(self::CONFIG_NAME); + $this->editableConfig = $configFactory->getEditable(self::CONFIG_NAME); } /** * Get test mode. */ public function getTestMode(): bool { - return (bool) $this->get('test_mode', TRUE); + return (bool) $this->get(self::TEST_MODE, TRUE); } /** @@ -49,18 +65,25 @@ public function getTestMode(): bool { * @phpstan-return array */ public function getSender(): array { - $value = $this->get('sender'); + $value = $this->get(self::SENDER); + return is_array($value) ? $value : []; } + /** + * Get key. + */ + public function getKey(): ?string { + return $this->get([self::CERTIFICATE, self::KEY]); + } + /** * Get certificate. - * - * @phpstan-return array */ - public function getCertificate(): array { - $value = $this->get('certificate'); - return is_array($value) ? $value : []; + public function getCertificateKey(): ?KeyInterface { + return $this->keyRepository->getKey( + $this->getKey(), + ); } /** @@ -69,57 +92,82 @@ public function getCertificate(): array { * @phpstan-return array */ public function getProcessing(): array { - $value = $this->get('processing'); + $value = $this->get(self::PROCESSING); + return is_array($value) ? $value : []; } /** - * Get a setting value. + * Get editable value. * - * @param string $key + * @param string|array $key * The key. - * @param mixed|null $default - * The default value. * * @return mixed - * The setting value. + * The editable value. */ - private function get(string $key, $default = NULL) { - $resolver = $this->getSettingsResolver(); - if (!$resolver->isDefined($key)) { - throw new InvalidSettingException(sprintf('Setting %s is not defined', $key)); + public function getEditableValue(string|array $key): mixed { + if (is_array($key)) { + $key = implode('.', $key); } - - return $this->store->get($key, $default); + return $this->editableConfig->get($key); } /** - * Set settings. + * Get runtime value override if any. * - * @throws \Symfony\Component\OptionsResolver\Exception\ExceptionInterface + * @param string|array $key + * The key. * - * @phpstan-param array $settings + * @return array|null + * - 'runtime': the runtime value + * - 'editable': the editable (raw) value */ - public function setSettings(array $settings): self { - $settings = $this->getSettingsResolver()->resolve($settings); - foreach ($settings as $key => $value) { - $this->store->set($key, $value); + public function getOverride(string|array $key): ?array { + $runtimeValue = $this->getRuntimeValue($key); + $editableValue = $this->getEditableValue($key); + + // Note: We deliberately use "Equal" (==) rather than "Identical" (===) + // to compare values (cf. https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison). + if ($runtimeValue == $editableValue) { + return NULL; } - return $this; + return [ + 'runtime' => $runtimeValue, + 'editable' => $editableValue, + ]; } /** - * Get settings resolver. + * Get a setting value. + * + * @param string|array $key + * The key. + * @param mixed $default + * The default value. + * + * @return mixed + * The setting value. */ - private function getSettingsResolver(): OptionsResolver { - return (new OptionsResolver()) - ->setDefaults([ - 'test_mode' => TRUE, - 'sender' => [], - 'certificate' => [], - 'processing' => [], - ]); + private function get(string|array $key, mixed $default = NULL) { + return $this->getRuntimeValue($key) ?? $default; + } + + /** + * Get runtime value with any overrides applied. + * + * @param string|array $key + * The key. + * + * @return mixed + * The runtime value. + */ + public function getRuntimeValue(string|array $key): mixed { + if (is_array($key)) { + $key = implode('.', $key); + } + return $this->runtimeConfig->get($key); } } diff --git a/package.json b/package.json deleted file mode 100644 index 52fcd34c..00000000 --- a/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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/scripts/code-analysis b/scripts/code-analysis index 9fec0f46..ace9e282 100755 --- a/scripts/code-analysis +++ b/scripts/code-analysis @@ -16,9 +16,21 @@ if [ ! -f "$drupal_dir/composer.json" ]; then composer --no-interaction create-project drupal/recommended-project:^10 "$drupal_dir" fi # Copy our code into the modules folder -mkdir -p "$drupal_dir/$module_path" + +# Clean up +rm -fr "${drupal_dir:?}/$module_path" + # https://stackoverflow.com/a/15373763 -rsync --archive --compress . --filter=':- .gitignore' --exclude "$drupal_dir" --exclude .git "$drupal_dir/$module_path" +# rsync --archive --compress . --filter=':- .gitignore' --exclude "$drupal_dir" --exclude .git "$drupal_dir/$module_path" + +# The rsync command in not available in itkdev/php8.1-fpm + +git config --global --add safe.directory /app +# Copy module files into module path +for f in $(git ls-files); do + mkdir -p "$drupal_dir/$module_path/$(dirname "$f")" + cp "$f" "$drupal_dir/$module_path/$f" +done drupal_composer config minimum-stability dev @@ -37,4 +49,4 @@ drupal_composer config extra.merge-plugin.include "$module_path/composer.json" drupal_composer require --dev symfony/phpunit-bridge # Run PHPStan -(cd "$drupal_dir" && vendor/bin/phpstan --configuration="$module_path/phpstan.neon") +(cd "$drupal_dir/$module_path" && ../../../../vendor/bin/phpstan) From 984bb8ea002e7b0f5561a58255cf2e0164561345 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 7 May 2024 14:26:39 +0200 Subject: [PATCH 30/88] Cleaned up Markdown --- modules/os2forms_attachment/README.md | 14 ++++++++------ modules/os2forms_autocomplete/README.md | 9 +++++---- modules/os2forms_dawa/README.md | 7 ++++--- modules/os2forms_forloeb/CHANGELOG.md | 9 +++++++-- modules/os2forms_forloeb/README.md | 16 ++++++++-------- modules/os2forms_nemid/README.md | 5 ++--- modules/os2forms_permissions_by_term/README.md | 7 ++++++- modules/os2forms_sbsys/README.md | 1 + modules/os2forms_webform_maps/README.md | 3 +++ 9 files changed, 44 insertions(+), 27 deletions(-) diff --git a/modules/os2forms_attachment/README.md b/modules/os2forms_attachment/README.md index f732ad2f..5b1c7501 100644 --- a/modules/os2forms_attachment/README.md +++ b/modules/os2forms_attachment/README.md @@ -1,20 +1,22 @@ # OS2Forms Attachment Drupal module -# Module purpose +## Module purpose The aim of this module is to provide an OS2forms attachment element for adding PDF/HTML attachment. It also supports creation of reusable headers/footers components which are used when rendering the attachments. -# How does it work +## How does it work -To add custom headers/footer ```admin/structure/webform/config/os2forms_attachment_component``` +To add custom headers/footer `admin/structure/webform/config/os2forms_attachment_component` -To specify headers/footers that will override the default ones on a global level (**Third party settings** -> **Entity print** section): ```admin/structure/webform/config``` +To specify headers/footers that will override the default ones on a global level (**Third party settings** -> **Entity +print** section): `admin/structure/webform/config` -To specify headers/footers that will override the default ones on a form level (**Third party settings** -> **Entity print** section): ```/admin/structure/webform/manage/[webform]/settings``` +To specify headers/footers that will override the default ones on a form level (**Third party settings** -> **Entity +print** section): ```/admin/structure/webform/manage/[webform]/settings``` -# Overwriting templates +## Overwriting templates With some setups it might be necessary to overwrite templates in order to access stylesheets or images. diff --git a/modules/os2forms_autocomplete/README.md b/modules/os2forms_autocomplete/README.md index d5902385..2294d45e 100644 --- a/modules/os2forms_autocomplete/README.md +++ b/modules/os2forms_autocomplete/README.md @@ -1,15 +1,16 @@ # OS2Forms Autocomplete Drupal module -# Module purpose +## Module purpose -The aim of this module is to provide a genetic OS2Forms Autocomplete element which can return options from an external webservice. +The aim of this module is to provide a genetic OS2Forms Autocomplete element which can return options from an external +webservice. -# How does it work +## How does it work Module exposes OS2Forms Autocomplete component that are available in the webform build process. Build page: -``` +```url admin/structure/webform/manage/[webform] ``` diff --git a/modules/os2forms_dawa/README.md b/modules/os2forms_dawa/README.md index 53876ba3..ae242ade 100644 --- a/modules/os2forms_dawa/README.md +++ b/modules/os2forms_dawa/README.md @@ -1,10 +1,11 @@ # OS2Forms DAWA Drupal module -# Module purpose +## Module purpose -The aim of this module is to provide integration with Danish Addresses Web API (DAWA https://dawa.aws.dk) and provider address autocomplete fields. +The aim of this module is to provide integration with Danish Addresses Web API (DAWA ) and provider +address autocomplete fields. -# How does it work +## How does it work Module exposes couple of new Autocomplete components that are available in the webform build process. diff --git a/modules/os2forms_forloeb/CHANGELOG.md b/modules/os2forms_forloeb/CHANGELOG.md index 3338d105..6d569821 100644 --- a/modules/os2forms_forloeb/CHANGELOG.md +++ b/modules/os2forms_forloeb/CHANGELOG.md @@ -1,4 +1,5 @@ # OS2Forms Forløb Change Log + All notable changes to this project should be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) @@ -17,9 +18,11 @@ before starting to add changes. ## 2.5.2 - 27.03.2023 ### Updated + - Bumped drupal/ultimate_cron version fixing [Deprecated function: Implicit conversion from float-string](https://www.drupal.org/project/ultimate_cron/issues/3256142). ## 2.5.1 - 10.03.2023 + - Added github action for checking changelog changes when creating pull requests - Added os2forms/os2forms dependency - Changed composer patching configuration @@ -30,18 +33,20 @@ before starting to add changes. ## 2.5.0 - 11.10.2022 ### Added + - retry task controller action - Added support for inheriting values without creating a submission ## 2.4.0 ### Added + - Github CI action for checking Drupal Coding standards with PHP Code Sniffer - Fixed coding standards issues - ## Example of change log record -``` + +```markdown ## [x.x.x] Release name ### Added - Description on added functionality. diff --git a/modules/os2forms_forloeb/README.md b/modules/os2forms_forloeb/README.md index 7cf06186..11d1b07b 100644 --- a/modules/os2forms_forloeb/README.md +++ b/modules/os2forms_forloeb/README.md @@ -4,11 +4,13 @@ Adds a Maestro workflow engine and advanced workflow functionality to OS2forms. ## Installing OS2forms 2.1 med Forløb -This module requires the codebase from the [OS2forms core project](https://github.com/OS2Forms/os2forms8) installed per the documentation and by selecting the os2forms_forloeb_profile at installation. After succesful installation you should have the OS2forms med Forløb Module available for install via gui. +This module requires the codebase from the [OS2forms core project](https://github.com/OS2Forms/os2forms8) installed per +the documentation and by selecting the os2forms_forloeb_profile at installation. After succesful installation you should +have the OS2forms med Forløb Module available for install via gui. You can also install the module by using Drush: -``` +```shell ./vendor/bin/drush pm:enable os2forms_forloeb ``` @@ -32,20 +34,18 @@ Settings for OS2Forms forløb are defined on `/admin/config/system/os2forms_forl #### Known anonymous roles -In order to make the notifications work, Maestro workflow tasks must be assigned -to a *known anonymous role* and these roles are defined under *Known anonymous -roles*. +In order to make the notifications work, Maestro workflow tasks must be assigned to a *known anonymous role* and these +roles are defined under *Known anonymous roles*. #### Processing -A notification is not sent to a user immediately, but added to a queue which -must be processed asynchronously. Specify the queue handling notification jobs. +A notification is not sent to a user immediately, but added to a queue which must be processed asynchronously. Specify +the queue handling notification jobs. #### Templates Define templates for emails and digital post (PDF). - To reference assets, e.g. stylesheet or images, in your templates, you can use the `base_url` Twig variable to get the base URL: diff --git a/modules/os2forms_nemid/README.md b/modules/os2forms_nemid/README.md index 98ec5532..ec7c7932 100644 --- a/modules/os2forms_nemid/README.md +++ b/modules/os2forms_nemid/README.md @@ -1,10 +1,10 @@ # OS2Forms Nemid Drupal module -# Module purpose +## Module purpose The aim of this module is to provide custom NemId field and integration with OS2Web Nemlogin module. -# How does it work +## How does it work Module exposes dozen of new NemID components that are available in the webform build process. @@ -17,4 +17,3 @@ Besides this module adds a special settings to the Third Party Webform settings: - Hide form if under address protection Settings: admin/structure/webform/manage/[webform]/settings - diff --git a/modules/os2forms_permissions_by_term/README.md b/modules/os2forms_permissions_by_term/README.md index 2c572931..33ac9367 100644 --- a/modules/os2forms_permissions_by_term/README.md +++ b/modules/os2forms_permissions_by_term/README.md @@ -1,10 +1,13 @@ # OS2Forms permission by term module + This module implements permission by term access restrictions on several lists and entity displays related to webform and maestro. ## Setup configuration + Add to your settings.php or local.settings.php -``` + +```php $config['permissions_by_term.settings'] = [ 'permissions_mode' => FALSE, 'require_all_terms_granted' => FALSE, @@ -12,6 +15,7 @@ $config['permissions_by_term.settings'] = [ 'target_bundles' => ['user_affiliation'] ] ``` + Alternative change your site configuration on admin/permissions-by-term/settings to match the above. !note This is the recommended configuration of the permissions_by_term module. Using different values for @@ -19,6 +23,7 @@ Alternative change your site configuration on admin/permissions-by-term/settings be thoroughly tested. ## Usage + - The user affiliation taxonomy is added to webform config form and Maestro workflow forms. - The Permissions by Term module adds a form element to the user form. - When a user visits an entity of the above mentioned this module checks for match between the entity and the users diff --git a/modules/os2forms_sbsys/README.md b/modules/os2forms_sbsys/README.md index b554eebc..8e1129f0 100644 --- a/modules/os2forms_sbsys/README.md +++ b/modules/os2forms_sbsys/README.md @@ -1,6 +1,7 @@ # OS2forms SBSYS integration Drupal module ## Module purpose + The aim of this module is to provide integration with SBSYS ESDH provider. ## How does it work diff --git a/modules/os2forms_webform_maps/README.md b/modules/os2forms_webform_maps/README.md index e6d08748..c36b901e 100644 --- a/modules/os2forms_webform_maps/README.md +++ b/modules/os2forms_webform_maps/README.md @@ -1,13 +1,16 @@ # OS2Forms Webform Maps module for Drupal 9 ## Module description + Provides integration with Leaflet maps and provides map element for webform. ## How does it work + The module provides a new element type for webform. The element type is called "OS2Forms Kort". The element type is based on the Leaflet library. The element type provides a map with a marker that can be moved around on the map. The element type also provides ways of changing layers on the map. The data can be exported to PDF. ## Installation + The module can be installed using the standard Drupal installation procedure. From 75d8ad226611f1f470b88bb07152f71bcb5a5eab Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 7 May 2024 23:30:35 +0200 Subject: [PATCH 31/88] Cleaned up --- .../os2forms_digital_post.install | 2 +- .../os2forms_digital_post.services.yml | 2 +- .../Commands/DigitalPostTestCommands.php | 4 +- .../src/Form/SettingsForm.php | 6 ++- .../src/Helper/DigitalPostHelper.php | 6 +-- .../src/Helper/KeyCertificateLocator.php | 10 ++-- .../src/Helper/MemoryCertificateLocator.php | 49 ------------------- 7 files changed, 17 insertions(+), 62 deletions(-) delete mode 100644 modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php diff --git a/modules/os2forms_digital_post/os2forms_digital_post.install b/modules/os2forms_digital_post/os2forms_digital_post.install index 80b756ac..f8140579 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.install +++ b/modules/os2forms_digital_post/os2forms_digital_post.install @@ -21,7 +21,7 @@ function os2forms_digital_post_schema() { /** * Implements hook_update_N(). */ -function os2forms_digital_post_update_9001() { +function os2forms_digital_post_update_9001(): void { \Drupal::service('module_installer')->install([ 'os2web_key', ], TRUE); diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 66bc3132..9b096fad 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -32,7 +32,7 @@ services: Drupal\os2forms_digital_post\Helper\DigitalPostHelper: arguments: - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@Drupal\\os2web_key\\CertificateHelper" + - "@Drupal\\os2web_key\\KeyHelper" - "@plugin.manager.os2web_datalookup" - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" diff --git a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php index 248452a3..8d7d17c0 100644 --- a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php +++ b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php @@ -145,7 +145,9 @@ private function dumpDigitalPostSettings(SymfonyStyle $io): void { Yaml::encode([ 'testMode' => $this->digitalPostSettings->getTestMode(), 'sender' => $this->digitalPostSettings->getSender(), - 'certificate' => $this->digitalPostSettings->getCertificate(), + 'certificate' => [ + 'key' => $this->digitalPostSettings->getKey(), + ], 'processing' => $this->digitalPostSettings->getProcessing(), ]), '', diff --git a/modules/os2forms_digital_post/src/Form/SettingsForm.php b/modules/os2forms_digital_post/src/Form/SettingsForm.php index 6b2e062c..b91e2ed5 100644 --- a/modules/os2forms_digital_post/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_post/src/Form/SettingsForm.php @@ -55,7 +55,7 @@ public static function create(ContainerInterface $container) { /** * {@inheritdoc} * - * @phpstan-return array + * @phpstan-return string[] */ protected function getEditableConfigNames() { return [ @@ -202,13 +202,15 @@ public function submitForm(array &$form, FormStateInterface $form_state): void { /** * Create form field description with information on any runtime override. * - * @param string|array $key + * @param string|array $key * The key. * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $description * The actual field description. * * @return string * The full description. + * + * @phpstan-param string|string[] $key */ private function createDescription(string|array $key, ?TranslatableMarkup $description = NULL): string { if ($value = $this->settings->getOverride($key)) { diff --git a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php index fc2355af..80825dff 100644 --- a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php +++ b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php @@ -11,7 +11,7 @@ use Drupal\os2web_datalookup\Plugin\DataLookupManager; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCompanyInterface; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupCprInterface; -use Drupal\os2web_key\CertificateHelper; +use Drupal\os2web_key\KeyHelper; use Drupal\webform\WebformSubmissionInterface; use ItkDev\Serviceplatformen\Service\SF1601\SF1601; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; @@ -30,7 +30,7 @@ final class DigitalPostHelper implements LoggerInterface { */ public function __construct( private readonly Settings $settings, - private readonly CertificateHelper $certificateHelper, + private readonly KeyHelper $keyHelper, private readonly DataLookupManager $dataLookupManager, private readonly MeMoHelper $meMoHelper, private readonly ForsendelseHelper $forsendelseHelper, @@ -65,7 +65,7 @@ public function sendDigitalPost(string $type, Message $message, ?ForsendelseI $f 'authority_cvr' => $senderSettings[Settings::SENDER_IDENTIFIER], 'certificate_locator' => new KeyCertificateLocator( $this->settings->getCertificateKey(), - $this->certificateHelper + $this->keyHelper ), ]; $service = new SF1601($options); diff --git a/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php b/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php index 832bc19f..8d8f0a41 100644 --- a/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php +++ b/modules/os2forms_digital_post/src/Helper/KeyCertificateLocator.php @@ -3,7 +3,7 @@ namespace Drupal\os2forms_digital_post\Helper; use Drupal\key\KeyInterface; -use Drupal\os2web_key\CertificateHelper; +use Drupal\os2web_key\KeyHelper; use ItkDev\Serviceplatformen\Certificate\AbstractCertificateLocator; use ItkDev\Serviceplatformen\Certificate\Exception\CertificateLocatorException; @@ -15,16 +15,16 @@ class KeyCertificateLocator extends AbstractCertificateLocator { /** * The parsed certificates. * - * @var array + * @var array */ - private readonly array $certificates; + private array $certificates; /** * Constructor. */ public function __construct( private readonly KeyInterface $key, - private readonly CertificateHelper $certificateHelper, + private readonly KeyHelper $keyHelper, ) { parent::__construct(); } @@ -36,7 +36,7 @@ public function __construct( */ public function getCertificates(): array { if (!isset($this->certificates)) { - $this->certificates = $this->certificateHelper->getCertificates($this->key); + $this->certificates = $this->keyHelper->getCertificates($this->key); } return $this->certificates; diff --git a/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php b/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php deleted file mode 100644 index 69d791d2..00000000 --- a/modules/os2forms_digital_post/src/Helper/MemoryCertificateLocator.php +++ /dev/null @@ -1,49 +0,0 @@ - - */ - public function getCertificates(): array { - $certificates = []; - $this->passphrase = 'P5bISuw?s:u4'; - if (!openssl_pkcs12_read($this->certificate, $certificates, $this->passphrase)) { - throw new CertificateLocatorException(sprintf('Could not read certificate: %s', openssl_error_string() ?: '')); - } - - return $certificates; - } - - /** - * {@inheritdoc} - */ - public function getCertificate(): string { - return $this->certificate; - } - - /** - * {@inheritdoc} - */ - public function getAbsolutePathToCertificate(): string { - throw new CertificateLocatorException(__METHOD__ . ' should not be used.'); - } - -} From d315e882869861940dad43233bca5e0ce7ee4694 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 13 May 2024 09:11:55 +0200 Subject: [PATCH 32/88] Required os2web_key 1.0 --- composer.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/composer.json b/composer.json index f997c480..2be78402 100644 --- a/composer.json +++ b/composer.json @@ -59,7 +59,7 @@ "itk-dev/serviceplatformen": "^1.5", "os2web/os2web_audit": "^0.1.6", "os2web/os2web_datalookup": "^2.0", - "os2web/os2web_key": "dev-os2web_key", + "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "os2web/os2web_simplesaml": "dev-master", "php-http/guzzle7-adapter": "^1.0", @@ -81,10 +81,6 @@ "wsdltophp/packagegenerator": "^4.0" }, "repositories": { - "os2web/os2web_key": { - "type": "vcs", - "url": "https://github.com/itk-dev/os2web_key" - }, "drupal": { "type": "composer", "url": "https://packages.drupal.org/8" From 1b0c85da60fa52426eb51a627e46764d7e015d35 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Mon, 13 May 2024 11:27:36 +0200 Subject: [PATCH 33/88] Updated os2web_datalookup --- composer.json | 6 +++++- modules/os2forms_digital_post/README.md | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2be78402..32301d1b 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "itk-dev/beskedfordeler-drupal": "^1.0", "itk-dev/serviceplatformen": "^1.5", "os2web/os2web_audit": "^0.1.6", - "os2web/os2web_datalookup": "^2.0", + "os2web/os2web_datalookup": "dev-feature/os2web_key as 1.12.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "os2web/os2web_simplesaml": "dev-master", @@ -81,6 +81,10 @@ "wsdltophp/packagegenerator": "^4.0" }, "repositories": { + "os2web/os2web_datalookup": { + "type": "vcs", + "url": "https://github.com/itk-dev/os2web_datalookup" + }, "drupal": { "type": "composer", "url": "https://packages.drupal.org/8" diff --git a/modules/os2forms_digital_post/README.md b/modules/os2forms_digital_post/README.md index c36e9f48..999d87c7 100644 --- a/modules/os2forms_digital_post/README.md +++ b/modules/os2forms_digital_post/README.md @@ -33,7 +33,8 @@ digital post. ### Key -We use [os2web_key](https://github.com/OS2web/os2web_key) to provide the certificate for sending digital post. +We use [os2web_key](https://github.com/OS2web/os2web_key) to provide the certificate for sending digital post, and the +key must be of type "[Certificate](https://github.com/os2web/os2web_key?tab=readme-ov-file#certificate)". ### Queue From ba5dd00f577e4cf0196fe1194b2ac968774d0c1c Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 17 Dec 2024 23:26:21 +0100 Subject: [PATCH 34/88] Cleaned up composer.json --- composer.json | 48 ++++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/composer.json b/composer.json index 32301d1b..01244961 100644 --- a/composer.json +++ b/composer.json @@ -57,8 +57,9 @@ "http-interop/http-factory-guzzle": "^1.0.0", "itk-dev/beskedfordeler-drupal": "^1.0", "itk-dev/serviceplatformen": "^1.5", + "mglaman/composer-drupal-lenient": "^1.0", "os2web/os2web_audit": "^0.1.6", - "os2web/os2web_datalookup": "dev-feature/os2web_key as 1.12.0", + "os2web/os2web_datalookup": "dev-feature/os2web_key as 2.0.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "os2web/os2web_simplesaml": "dev-master", @@ -67,8 +68,7 @@ "symfony/options-resolver": "^5.4 || ^6.0", "webmozart/path-util": "^2.3", "wsdltophp/packagebase": "^5.0", - "zaporylie/composer-drupal-optimizations": "^1.2", - "mglaman/composer-drupal-lenient": "^1.0" + "zaporylie/composer-drupal-optimizations": "^1.2" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", @@ -101,6 +101,7 @@ "cweagans/composer-patches": true, "dealerdirect/phpcodesniffer-composer-installer": true, "ergebnis/composer-normalize": true, + "mglaman/composer-drupal-lenient": true, "phpstan/extension-installer": true, "simplesamlphp/composer-module-installer": true, "vaimo/composer-patches": true, @@ -110,8 +111,19 @@ }, "extra": { "composer-exit-on-patch-failure": false, + "drupal-lenient": { + "allowed-list": [ + "drupal/coc_forms_auto_export", + "drupal/webform_node_element" + ] + }, "enable-patching": true, "patches": { + "drupal/coc_forms_auto_export": { + "3240592 - Problem with phpseclib requirement in 2.x (https://www.drupal.org/project/coc_forms_auto_export/issues/3240592)": "https://www.drupal.org/files/issues/2021-10-04/requirement-namespace-3240592-1.patch", + "3286562 - Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2022-06-15/coc_forms_auto_export.2.0.x-dev.rector.patch", + "3259009 - PHP Warnings/Notices on Download Page": "https://git.drupalcode.org/project/coc_forms_auto_export/-/merge_requests/1.diff" + }, "drupal/entity_print": { "2733781 - Add Export to Word Support": "https://www.drupal.org/files/issues/2019-11-22/2733781-47.patch" }, @@ -119,26 +131,14 @@ "Unlock possibility of using Entity print module export to Word": "https://www.drupal.org/files/issues/2020-02-29/3096552-6.patch", "Webform computed element post save alter": "https://www.drupal.org/files/issues/2024-06-25/webform_computed_post_save_field_alter.patch", "Add custom hook (hook_webform_post_load_data) for audit logging": "https://gist.githubusercontent.com/cableman/d26898fc8f65ee0a31001bf391583b59/raw/6189dc4c2ceaabb19d25cc4b98b0b3028a6b0e1e/gistfile1.txt" - - }, - "drupal/coc_forms_auto_export": { - "3240592 - Problem with phpseclib requirement in 2.x (https://www.drupal.org/project/coc_forms_auto_export/issues/3240592)": "https://www.drupal.org/files/issues/2021-10-04/requirement-namespace-3240592-1.patch", - "3286562 - Automated Drupal 10 compatibility fixes" : "https://www.drupal.org/files/issues/2022-06-15/coc_forms_auto_export.2.0.x-dev.rector.patch", - "3259009 - PHP Warnings/Notices on Download Page" : "https://git.drupalcode.org/project/coc_forms_auto_export/-/merge_requests/1.diff" - }, - "drupal/webform_node_element": { - "3290637 - Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2023-05-12/webform_node_element_d10-3290637-11.patch" }, "drupal/webform_encrypt": { "Ensure data is base64 encoded (https://www.drupal.org/project/webform_encrypt/issues/3399414)": "https://git.drupalcode.org/project/webform_encrypt/-/merge_requests/4.patch", "PHP Warning if unserialize fails (https://www.drupal.org/project/webform_encrypt/issues/3292305)": "https://www.drupal.org/files/issues/2022-06-23/unserialize-php-notice.patch" + }, + "drupal/webform_node_element": { + "3290637 - Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2023-05-12/webform_node_element_d10-3290637-11.patch" } - }, - "drupal-lenient": { - "allowed-list": [ - "drupal/coc_forms_auto_export", - "drupal/webform_node_element" - ] } }, "scripts": { @@ -160,17 +160,5 @@ "coding-standards-check/phpcs": [ "phpcs --standard=phpcs.xml.dist" ] - }, - "config": { - "sort-packages": true, - "allow-plugins": { - "cweagans/composer-patches": true, - "dealerdirect/phpcodesniffer-composer-installer": true, - "phpstan/extension-installer": true, - "simplesamlphp/composer-module-installer": true, - "vaimo/composer-patches": true, - "zaporylie/composer-drupal-optimizations": true, - "mglaman/composer-drupal-lenient": true - } } } From a32eaae0c2426a7f1e46d26a0bb928dbd9fb93f2 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 17 Dec 2024 23:40:20 +0100 Subject: [PATCH 35/88] Cleaned up --- README.md | 1 + modules/os2forms_fasit/docs/BENYTTELSE.md | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 90245a21..44eb8e47 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.1-fpm composer c ### Markdown ```sh +docker pull peterdavehello/markdownlint docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' --fix docker run --rm --volume $PWD:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md' ``` diff --git a/modules/os2forms_fasit/docs/BENYTTELSE.md b/modules/os2forms_fasit/docs/BENYTTELSE.md index 63cc361d..cb8c958a 100644 --- a/modules/os2forms_fasit/docs/BENYTTELSE.md +++ b/modules/os2forms_fasit/docs/BENYTTELSE.md @@ -10,7 +10,7 @@ aftale hvilke certifikater der anvendes. Disse certifikater skal være OCES-3, f.eks. FOCES-3, og skal bruges i pem- eller cer-format. Dernæst oplyses det anvendte certifikats thumbprint eller public-key til Fasit, -som derefter aktiverer snitfladen. Se evt. +som derefter aktiverer snitfladen. Se evt. [README#certificate](../README.md#certificate) for hvordan et certifikats thumbprint kan findes gennem kommandolinjen. @@ -22,7 +22,7 @@ Her skal følgende sættes op: * Fasit API base url * Basis url’en til Fasit. Denne specificeres af Fasit. - * Eksempel: https://webservices.fasit.dk/ + * Eksempel: * Fasit API tenant * Fasit tenant. Denne specificeres af Fasit. * Eksempel: aarhus @@ -30,14 +30,14 @@ Her skal følgende sættes op: * Hvilken version af af API’et der skal bruges. Her er mulighederne ’v1’ eller ’v2’. Der bør altid bruges ’v2’. * Eksempel: v2 * Certificate - * Her kan angives detaljer til et azure key vault hvori certifikatet ligges (Azure key vault) eller en sti direkte til certifikatet (Filsystem) + * Her kan angives detaljer til et azure key vault hvori certifikatet ligges (Azure key vault) eller en sti direkte til + certifikatet (Filsystem) * Passphrase * Passphrase til certifikatet, hvis sådan et eksisterer. - Se evt. Fasit Scultz dokumentationen for flere detaljer på opbygningen af endpoint url’er. -Det er desuden muligt at teste om os2forms kan få fat i certifikatet på samme konfigurations-side. +Det er desuden muligt at teste om os2forms kan få fat i certifikatet på samme konfigurations-side. ## Handler From cd3e855363ba56d908697b082fee4a8867a126d4 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 20 Dec 2024 15:27:57 +0100 Subject: [PATCH 36/88] Updated --- .github/workflows/pr.yml | 6 ++-- .../DataLookup/DatafordelerDataLookup.php | 28 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c78e9623..6eb23e77 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [ '8.1' ] + php-versions: [ '8.3' ] dependency-version: [ prefer-lowest, prefer-stable ] steps: - uses: actions/checkout@master @@ -59,7 +59,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [ '8.1' ] + php-versions: [ '8.3' ] steps: - uses: actions/checkout@master - name: Setup PHP, with composer and extensions @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [ '8.1' ] + php-versions: [ '8.3' ] steps: - uses: actions/checkout@master - name: Setup PHP, with composer and extensions diff --git a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php index ba699519..9d3b1aaa 100644 --- a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php +++ b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php @@ -3,8 +3,11 @@ namespace Drupal\os2forms_dawa\Plugin\os2web\DataLookup; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\File\FileSystem; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\key\KeyRepository; +use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_dawa\Entity\DatafordelerMatrikula; use Drupal\os2web_audit\Service\Logger; use Drupal\os2web_datalookup\Plugin\os2web\DataLookup\DataLookupBase; @@ -21,13 +24,6 @@ */ class DatafordelerDataLookup extends DataLookupBase implements DatafordelerDataLookupInterface, ContainerFactoryPluginInterface { - /** - * The HTTP client to fetch the feed data with. - * - * @var \GuzzleHttp\ClientInterface - */ - protected $httpClient; - /** * {@inheritdoc} */ @@ -35,23 +31,33 @@ public function __construct( array $configuration, $plugin_id, $plugin_definition, - ClientInterface $httpClient, + protected ClientInterface $httpClient, Logger $auditLogger, + KeyRepositoryInterface $keyRepository, + FileSystem $fileSystem, ) { - $this->httpClient = $httpClient; - parent::__construct($configuration, $plugin_id, $plugin_definition, $auditLogger); + parent::__construct($configuration, $plugin_id, $plugin_definition, $auditLogger, $keyRepository, $fileSystem); } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + /** @var Logger $auditLogger */ + $auditLogger = $container->get('os2web_audit.logger'); + /** @var \Drupal\key\KeyRepositoryInterface $keyRepository */ + $keyRepository = $container->get('key.repository'); + /** @var \Drupal\Core\File\FileSystem $fileSystem */ + $fileSystem = $container->get('file_system'); + return new static( $configuration, $plugin_id, $plugin_definition, $container->get('http_client'), - $container->get('os2web_audit.logger'), + $auditLogger, + $keyRepository, + $fileSystem, ); } From 7749375abec39a6bfd49f4ebcfdb49bea3197727 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 22 Jan 2025 12:23:32 +0100 Subject: [PATCH 37/88] Rebase --- CHANGELOG.md | 9 ++++++++- composer.json | 7 ++++--- .../os2web/DataLookup/DatafordelerDataLookup.php | 1 + .../src/Helper/CertificateLocatorHelper.php | 0 modules/os2forms_fbs_handler/src/Client/FBS.php | 3 +++ .../os2forms_fbs_handler/src/Client/Model/Guardian.php | 2 ++ .../os2forms_fbs_handler/src/Client/Model/Patron.php | 6 ++++++ .../src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php | 4 ++++ .../src/Plugin/WebformHandler/FbsWebformHandler.php | 10 ++++++++++ phpstan.neon | 1 + 10 files changed, 39 insertions(+), 4 deletions(-) delete mode 100644 modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba06141..e849be68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ before starting to add changes. Use example [placed in the end of the page](#exa - Added support for `os2web_key` in Fasit handler. - Switched from saving settings in key value store to config, i.e the module needs to be reconfigured. +- Removed modules ldap_auth, logging_alerts, maillog + +## [3.21.2] 2025-01-07 + +- Adds missing `http-message-util` requirement and use statement. +- Runs code-analysis on `os2forms_fbs_handler` module. ## [3.21.1] 2025-01-06 @@ -334,7 +340,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa - Security in case of vulnerabilities. ``` -[Unreleased]: https://github.com/OS2Forms/os2forms/compare/3.21.1...HEAD +[Unreleased]: https://github.com/OS2Forms/os2forms/compare/3.21.2...HEAD +[3.21.2]: https://github.com/OS2Forms/os2forms/compare/3.21.1...3.21.2 [3.21.1]: https://github.com/OS2Forms/os2forms/compare/3.21.0...3.21.1 [3.21.0]: https://github.com/OS2Forms/os2forms/compare/3.20.1...3.21.0 [3.20.1]: https://github.com/OS2Forms/os2forms/compare/3.20.0...3.20.1 diff --git a/composer.json b/composer.json index 2b83bd51..01244961 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "dompdf/dompdf": "^2.0", "drupal/admin_toolbar": "^3.0", "drupal/advancedqueue": "^1.0", + "drupal/cache_control_override": "^1.1|^2.0", "drupal/clientside_validation": "^4.0", "drupal/coc_forms_auto_export": "^2.0@alpha", "drupal/config_entity_revisions": "dev-2.0.x", @@ -19,14 +20,13 @@ "drupal/entity_print": "^2.1", "drupal/eu_cookie_compliance": "^1.8", "drupal/events_logging": "^2.0@beta", + "drupal/gin": "^3.0-rc", "drupal/honeypot": "^2.0", "drupal/image_widget_crop": "^2.3", "drupal/ldap": "^4.2", - "drupal/ldap_auth": "^1.17", "drupal/leaflet": "^10.0", "drupal/leaflet_layers": "^1.1", "drupal/libraries": "^4.0", - "drupal/logging_alerts": "^2.0", "drupal/maestro": "^3.1", "drupal/mailsystem": "^4.1", "drupal/masquerade": "^2.0@RC", @@ -53,6 +53,7 @@ "drupal/webform_validation": "^2.0", "drupal/webform_views": "^5.0@alpha", "drupal/workflow_participants": "^3.0@RC", + "fig/http-message-util": "^1.1", "http-interop/http-factory-guzzle": "^1.0.0", "itk-dev/beskedfordeler-drupal": "^1.0", "itk-dev/serviceplatformen": "^1.5", @@ -61,6 +62,7 @@ "os2web/os2web_datalookup": "dev-feature/os2web_key as 2.0.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", + "os2web/os2web_simplesaml": "dev-master", "php-http/guzzle7-adapter": "^1.0", "phpoffice/phpword": "^0.18.2", "symfony/options-resolver": "^5.4 || ^6.0", @@ -71,7 +73,6 @@ "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", "drupal/coder": "^8.3", - "drupal/maillog": "^1.0", "ergebnis/composer-normalize": "^2.42", "mglaman/phpstan-drupal": "^1.1", "phpstan/extension-installer": "^1.3", diff --git a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php index 0e50ffaf..f0e5af69 100644 --- a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php +++ b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php @@ -6,6 +6,7 @@ use Drupal\Core\File\FileSystem; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\key\KeyRepository; use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_dawa\Entity\DatafordelerMatrikula; use Drupal\os2web_audit\Service\Logger; diff --git a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/os2forms_fbs_handler/src/Client/FBS.php b/modules/os2forms_fbs_handler/src/Client/FBS.php index 44b8f6a7..66c38b4d 100644 --- a/modules/os2forms_fbs_handler/src/Client/FBS.php +++ b/modules/os2forms_fbs_handler/src/Client/FBS.php @@ -4,6 +4,7 @@ use Drupal\os2forms_fbs_handler\Client\Model\Guardian; use Drupal\os2forms_fbs_handler\Client\Model\Patron; +use Fig\Http\Message\RequestMethodInterface; use GuzzleHttp\Client; use Symfony\Component\HttpFoundation\Request; @@ -242,6 +243,8 @@ public function createGuardian(Patron $patron, Guardian $guardian): int { * * @throws \GuzzleHttp\Exception\GuzzleException * @throws \JsonException + * + * @phpstan-param array|string $data */ private function request(string $uri, array|string $data, string $method = Request::METHOD_POST): mixed { $url = rtrim($this->endpoint, '/\\'); diff --git a/modules/os2forms_fbs_handler/src/Client/Model/Guardian.php b/modules/os2forms_fbs_handler/src/Client/Model/Guardian.php index 371d3bba..b72a648f 100644 --- a/modules/os2forms_fbs_handler/src/Client/Model/Guardian.php +++ b/modules/os2forms_fbs_handler/src/Client/Model/Guardian.php @@ -22,6 +22,8 @@ public function __construct( * * @return array * Array with field required by FBS calls. + * + * @phpstan-return array */ public function toArray(): array { return [ diff --git a/modules/os2forms_fbs_handler/src/Client/Model/Patron.php b/modules/os2forms_fbs_handler/src/Client/Model/Patron.php index e5e7d562..db3e1d0f 100644 --- a/modules/os2forms_fbs_handler/src/Client/Model/Patron.php +++ b/modules/os2forms_fbs_handler/src/Client/Model/Patron.php @@ -9,6 +9,10 @@ final class Patron { /** * Default constructor. + * + * @phpstan-param array|null $notificationProtocols + * @phpstan-param array|null $onHold + * @phpstan-param array|null $emailAddresses */ public function __construct( public readonly ?string $patronId = NULL, @@ -35,6 +39,8 @@ public function __construct( * * @return array * Array with field required by FBS calls. + * + * @phpstan-return array */ public function toArray(): array { return [ diff --git a/modules/os2forms_fbs_handler/src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php b/modules/os2forms_fbs_handler/src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php index 179d0a1a..d5de9afa 100644 --- a/modules/os2forms_fbs_handler/src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php +++ b/modules/os2forms_fbs_handler/src/Plugin/AdvancedQueue/JobType/FbsCreateUser.php @@ -35,6 +35,8 @@ final class FbsCreateUser extends JobTypeBase implements ContainerFactoryPluginI /** * {@inheritdoc} + * + * @phpstan-param array $configuration */ public function __construct( array $configuration, @@ -50,6 +52,8 @@ public function __construct( /** * {@inheritdoc} + * + * @phpstan-param array $configuration */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( diff --git a/modules/os2forms_fbs_handler/src/Plugin/WebformHandler/FbsWebformHandler.php b/modules/os2forms_fbs_handler/src/Plugin/WebformHandler/FbsWebformHandler.php index 25625145..1c2abad2 100644 --- a/modules/os2forms_fbs_handler/src/Plugin/WebformHandler/FbsWebformHandler.php +++ b/modules/os2forms_fbs_handler/src/Plugin/WebformHandler/FbsWebformHandler.php @@ -45,6 +45,8 @@ final class FbsWebformHandler extends WebformHandlerBase { /** * Constructs an FbsWebformHandler object. + * + * @phpstan-param array $configuration */ public function __construct( array $configuration, @@ -70,6 +72,8 @@ public function __construct( /** * {@inheritdoc} + * + * @phpstan-param array $configuration */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( @@ -87,6 +91,10 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} + * + * @phpstan-param array $form + * + * @phpstan-return array */ public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { if (is_null($this->getQueue())) { @@ -143,6 +151,8 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta /** * {@inheritdoc} + * + * @phpstan-param array $form */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void { parent::submitConfigurationForm($form, $form_state); diff --git a/phpstan.neon b/phpstan.neon index 8ece9b1d..dd1fcc1d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,6 +2,7 @@ parameters: level: 6 paths: - modules/os2forms_digital_post/ + - modules/os2forms_fbs_handler/ excludePaths: # @see https://github.com/mglaman/drupal-check/issues/261#issuecomment-1030141772/ - vendor From 4c601629fdb53bf8d08eba8cd0bcfdda9f1444e9 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 22 Jan 2025 12:28:02 +0100 Subject: [PATCH 38/88] Applied coding standards --- composer.json | 2 +- .../src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 01244961..aee475b2 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "dompdf/dompdf": "^2.0", "drupal/admin_toolbar": "^3.0", "drupal/advancedqueue": "^1.0", - "drupal/cache_control_override": "^1.1|^2.0", + "drupal/cache_control_override": "^1.1 || ^2.0", "drupal/clientside_validation": "^4.0", "drupal/coc_forms_auto_export": "^2.0@alpha", "drupal/config_entity_revisions": "dev-2.0.x", diff --git a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php index f0e5af69..0e50ffaf 100644 --- a/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php +++ b/modules/os2forms_dawa/src/Plugin/os2web/DataLookup/DatafordelerDataLookup.php @@ -6,7 +6,6 @@ use Drupal\Core\File\FileSystem; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\key\KeyRepository; use Drupal\key\KeyRepositoryInterface; use Drupal\os2forms_dawa\Entity\DatafordelerMatrikula; use Drupal\os2web_audit\Service\Logger; From 1964c27043c31b6f6b83dcb4ba7fdd96ff03dd2b Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 22 Jan 2025 14:58:56 +0100 Subject: [PATCH 39/88] Updated Digital post command --- .../os2forms_digital_post/drush.services.yml | 10 ---------- .../Commands/DigitalPostTestCommands.php | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 12 deletions(-) delete mode 100644 modules/os2forms_digital_post/drush.services.yml rename modules/os2forms_digital_post/src/{ => Drush}/Commands/DigitalPostTestCommands.php (91%) diff --git a/modules/os2forms_digital_post/drush.services.yml b/modules/os2forms_digital_post/drush.services.yml deleted file mode 100644 index 7339bc2a..00000000 --- a/modules/os2forms_digital_post/drush.services.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - os2forms_digital_post.commands: - class: \Drupal\os2forms_digital_post\Commands\DigitalPostTestCommands - arguments: - - '@Drupal\os2forms_digital_post\Helper\DigitalPostHelper' - - '@token' - - '@plugin.manager.entity_print.print_engine' - - '@Drupal\os2forms_digital_post\Helper\Settings' - tags: - - { name: drush.command } diff --git a/modules/os2forms_digital_post/src/Commands/DigitalPostTestCommands.php b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php similarity index 91% rename from modules/os2forms_digital_post/src/Commands/DigitalPostTestCommands.php rename to modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php index 5e9bdbbf..26d65faf 100644 --- a/modules/os2forms_digital_post/src/Commands/DigitalPostTestCommands.php +++ b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php @@ -1,6 +1,6 @@ get(DigitalPostHelper::class), + $container->get('token'), + $container->get('plugin.manager.entity_print.print_engine'), + $container->get(Settings::class), + ); + } + /** * Send digital post. * From c58ec55c6139ae9187bbbe1bf30bee4a92549b12 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 22 Jan 2025 15:55:09 +0100 Subject: [PATCH 40/88] Coding standards --- .../src/Drush/Commands/DigitalPostTestCommands.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php index 26d65faf..759c8e25 100644 --- a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php +++ b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php @@ -34,7 +34,7 @@ public function __construct( /** * {@inheritdoc} */ - public static function create(ContainerInterface $container) { + public static function create(ContainerInterface $container): self { return new static( $container->get(DigitalPostHelper::class), $container->get('token'), From 0404c5ccc1b9058006baa39321dfd42d1cb97f71 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 22 Jan 2025 22:54:56 +0100 Subject: [PATCH 41/88] Used Autowire trait --- .../Drush/Commands/DigitalPostTestCommands.php | 17 ++++------------- .../src/Drush/Commands/FasitTestCommand.php | 12 ++---------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php index 759c8e25..6b23d874 100644 --- a/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php +++ b/modules/os2forms_digital_post/src/Drush/Commands/DigitalPostTestCommands.php @@ -3,6 +3,7 @@ namespace Drupal\os2forms_digital_post\Drush\Commands; use Drupal\Component\Serialization\Yaml; +use Drupal\Core\DependencyInjection\AutowireTrait; use Drupal\Core\Utility\Token; use Drupal\entity_print\Plugin\EntityPrintPluginManagerInterface; use Drupal\os2forms_digital_post\Helper\DigitalPostHelper; @@ -12,13 +13,14 @@ use ItkDev\Serviceplatformen\Service\SF1601\SF1601; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; /** * Test commands for digital post. */ final class DigitalPostTestCommands extends DrushCommands { + use AutowireTrait; /** * Constructor. @@ -26,23 +28,12 @@ final class DigitalPostTestCommands extends DrushCommands { public function __construct( private readonly DigitalPostHelper $digitalPostHelper, private readonly Token $token, + #[Autowire(service: 'plugin.manager.entity_print.print_engine')] private readonly EntityPrintPluginManagerInterface $entityPrintPluginManager, private readonly Settings $digitalPostSettings, ) { } - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container): self { - return new static( - $container->get(DigitalPostHelper::class), - $container->get('token'), - $container->get('plugin.manager.entity_print.print_engine'), - $container->get(Settings::class), - ); - } - /** * Send digital post. * diff --git a/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php b/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php index 586adc04..9d1fb281 100644 --- a/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php +++ b/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php @@ -2,14 +2,15 @@ namespace Drupal\os2forms_fasit\Drush\Commands; +use Drupal\Core\DependencyInjection\AutowireTrait; use Drupal\os2forms_fasit\Helper\FasitHelper; use Drush\Commands\DrushCommands; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * A Drush commandfile. */ final class FasitTestCommand extends DrushCommands { + use AutowireTrait; /** * Constructs a FasitTestCommand object. @@ -20,15 +21,6 @@ public function __construct( parent::__construct(); } - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get(FasitHelper::class), - ); - } - /** * Test API access. * From d8c7c2e7881800f785db347fda7e352d1d5002a3 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Thu, 23 Jan 2025 14:14:08 +0100 Subject: [PATCH 42/88] Updated digital post module to allow key and old configuration methods --- CHANGELOG.md | 2 + .../os2forms_digital_post.services.yml | 1 + .../src/Form/SettingsForm.php | 187 +++++++++++++++++- .../src/Helper/CertificateLocatorHelper.php | 88 +++++++++ .../src/Helper/DigitalPostHelper.php | 18 +- .../src/Helper/Settings.php | 10 + 6 files changed, 293 insertions(+), 13 deletions(-) create mode 100644 modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e849be68..d6fbf37f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa - [#101](https://github.com/OS2Forms/os2forms/pull/101) - Added support for `os2web_key` in Digital post + - Switched from saving settings in key value store to config, i.e + the module needs to be reconfigured. - Added support for `os2web_key` in Fasit handler. - Switched from saving settings in key value store to config, i.e the module needs to be reconfigured. diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 9b096fad..993a7208 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -37,6 +37,7 @@ services: - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" - "@Drupal\\os2forms_digital_post\\Helper\\BeskedfordelerHelper" + - "@Drupal\\os2forms_digital_post\\Helper\\CertificateLocatorHelper" - "@logger.channel.os2forms_digital_post" - "@logger.channel.os2forms_digital_post_submission" - "@os2web_audit.logger" diff --git a/modules/os2forms_digital_post/src/Form/SettingsForm.php b/modules/os2forms_digital_post/src/Form/SettingsForm.php index b91e2ed5..5bfb04f6 100644 --- a/modules/os2forms_digital_post/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_post/src/Form/SettingsForm.php @@ -11,6 +11,7 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; +use Drupal\os2forms_digital_post\Helper\CertificateLocatorHelper; use Drupal\os2forms_digital_post\Helper\Settings; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -34,6 +35,7 @@ public function __construct( ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entityTypeManager, private readonly Settings $settings, + private readonly CertificateLocatorHelper $certificateLocatorHelper, ) { parent::__construct($config_factory); $this->queueStorage = $entityTypeManager->getStorage('advancedqueue_queue'); @@ -49,6 +51,7 @@ public static function create(ContainerInterface $container) { $container->get('config.factory'), $container->get('entity_type.manager'), $container->get(Settings::class), + $container->get(CertificateLocatorHelper::class), ); } @@ -134,17 +137,127 @@ public function buildForm(array $form, FormStateInterface $form_state): array { '#type' => 'fieldset', '#title' => $this->t('Certificate'), '#tree' => TRUE, + ]; + + $form[Settings::CERTIFICATE][Settings::CERTIFICATE_PROVIDER] = [ + '#type' => 'select', + '#title' => $this->t('Provider'), + '#options' => [ + Settings::PROVIDER_TYPE_FORM => $this->t('Form'), + Settings::PROVIDER_TYPE_KEY => $this->t('Key'), + ], + '#default_value' => $this->settings->getEditableValue([Settings::CERTIFICATE, Settings::CERTIFICATE_PROVIDER]) ?? Settings::PROVIDER_TYPE_FORM, + '#description' => $this->t('Specifies which provider to use'), + ]; + + $form[Settings::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE] = [ + '#type' => 'select', + '#title' => $this->t('Certificate locator type'), + '#options' => [ + CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT => $this->t('Azure key vault'), + CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM => $this->t('File system'), + ], + '#default_value' => $this->settings->getEditableValue([ + Settings::CERTIFICATE, + CertificateLocatorHelper::LOCATOR_TYPE, + ]) ?? NULL, + '#states' => [ + 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_FORM]], + ], + '#description' => $this->t('Specifies which locator to use'), + ]; - Settings::KEY => [ - '#type' => 'key_select', - '#key_filters' => [ - 'type' => 'os2web_key_certificate', + $form[Settings::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT] = [ + '#type' => 'fieldset', + '#title' => $this->t('Azure key vault'), + '#states' => [ + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_FORM], + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT], + ], + ], + ]; + + $settings = [ + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_TENANT_ID => ['title' => $this->t('Tenant id')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_APPLICATION_ID => ['title' => $this->t('Application id')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_CLIENT_SECRET => ['title' => $this->t('Client secret')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_NAME => ['title' => $this->t('Name')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_SECRET => ['title' => $this->t('Secret')], + CertificateLocatorHelper::LOCATOR_AZURE_KEY_VAULT_VERSION => ['title' => $this->t('Version')], + ]; + + foreach ($settings as $key => $info) { + $form[Settings::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] = [ + '#type' => 'textfield', + '#title' => $info['title'], + '#default_value' => $this->settings->getEditableValue([ + Settings::CERTIFICATE, + CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT, + $key, + ]) ?? NULL, + '#states' => [ + 'required' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_FORM], + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT], + ], + ], + ]; + } + + $form[Settings::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM] = [ + '#type' => 'fieldset', + '#title' => $this->t('File system'), + '#states' => [ + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_FORM], + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM], + ], + ], + + CertificateLocatorHelper::LOCATOR_FILE_SYSTEM_PATH => [ + '#type' => 'textfield', + '#title' => $this->t('Path'), + '#default_value' => $this->settings->getEditableValue([ + Settings::CERTIFICATE, + CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM, + CertificateLocatorHelper::LOCATOR_FILE_SYSTEM_PATH, + ]) ?? NULL, + '#states' => [ + 'required' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_FORM], + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM], + ], ], - '#key_description' => FALSE, - '#title' => $this->t('Key'), - '#default_value' => $this->settings->getEditableValue([Settings::CERTIFICATE, Settings::KEY]), - '#required' => TRUE, - '#description' => $this->createDescription([Settings::CERTIFICATE, Settings::KEY]), + ], + ]; + + $form[Settings::CERTIFICATE][CertificateLocatorHelper::LOCATOR_PASSPHRASE] = [ + '#type' => 'textfield', + '#title' => $this->t('Passphrase'), + '#default_value' => $this->settings->getEditableValue([ + Settings::CERTIFICATE, + CertificateLocatorHelper::LOCATOR_PASSPHRASE, + ]) ?? '', + '#states' => [ + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_FORM], + ], + ], + ]; + + $form[Settings::CERTIFICATE][Settings::PROVIDER_TYPE_KEY] = [ + '#type' => 'key_select', + '#key_filters' => [ + 'type' => 'os2web_key_certificate', + ], + '#key_description' => FALSE, + '#title' => $this->t('Key'), + '#default_value' => $this->settings->getEditableValue([Settings::CERTIFICATE, Settings::PROVIDER_TYPE_KEY]), + '#required' => TRUE, + '#description' => $this->createDescription([Settings::CERTIFICATE, Settings::PROVIDER_TYPE_KEY]), + '#states' => [ + 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_KEY]], ], ]; @@ -176,15 +289,55 @@ public function buildForm(array $form, FormStateInterface $form_state): array { ), ]; + $form['actions']['testCertificate'] = [ + '#type' => 'submit', + '#name' => 'testCertificate', + '#value' => $this->t('Test certificate'), + '#states' => [ + 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_FORM]], + ], + ]; + return $form; } + /** + * {@inheritdoc} + * + * @phpstan-param array $form + */ + public function validateForm(array &$form, FormStateInterface $form_state): void { + $triggeringElement = $form_state->getTriggeringElement(); + if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { + return; + } + + $values = $form_state->getValues(); + + if (Settings::PROVIDER_TYPE_FORM === $values[Settings::CERTIFICATE][Settings::CERTIFICATE_PROVIDER]) { + if (CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM === $values[Settings::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE]) { + $path = $values[Settings::CERTIFICATE][CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM][CertificateLocatorHelper::LOCATOR_FILE_SYSTEM_PATH] ?? NULL; + if (!file_exists($path)) { + $form_state->setErrorByName('certificate][file_system][path', $this->t('Invalid certificate path: %path', ['%path' => $path])); + } + } + } + + parent::validateForm($form, $form_state); + } + /** * {@inheritdoc} * * @phpstan-param array $form */ public function submitForm(array &$form, FormStateInterface $form_state): void { + $triggeringElement = $form_state->getTriggeringElement(); + if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { + $this->testCertificate(); + return; + } + $config = $this->config(Settings::CONFIG_NAME); foreach ([ Settings::TEST_MODE, @@ -223,4 +376,20 @@ private function createDescription(string|array $key, ?TranslatableMarkup $descr return (string) $description; } + /** + * Test certificate. + */ + private function testCertificate(): void { + try { + + $certificateLocator = $this->certificateLocatorHelper->getCertificateLocator(); + $certificateLocator->getCertificates(); + $this->messenger()->addStatus($this->t('Certificate succesfully tested')); + } + catch (\Throwable $throwable) { + $message = $this->t('Error testing certificate: %message', ['%message' => $throwable->getMessage()]); + $this->messenger()->addError($message); + } + } + } diff --git a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php new file mode 100644 index 00000000..14710b93 --- /dev/null +++ b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php @@ -0,0 +1,88 @@ +settings->getEditableValue(Settings::CERTIFICATE); + + $locatorType = $certificateSettings['locator_type']; + $options = $certificateSettings[$locatorType]; + $options += [ + 'passphrase' => $certificateSettings['passphrase'] ?: '', + ]; + + if (self::LOCATOR_TYPE_AZURE_KEY_VAULT === $locatorType) { + $httpClient = new GuzzleAdapter(new Client()); + $requestFactory = new RequestFactory(); + + $vaultToken = new VaultToken($httpClient, $requestFactory); + + $token = $vaultToken->getToken( + $options['tenant_id'], + $options['application_id'], + $options['client_secret'], + ); + + $vault = new VaultSecret( + $httpClient, + $requestFactory, + $options['name'], + $token->getAccessToken() + ); + + return new AzureKeyVaultCertificateLocator( + $vault, + $options['secret'], + $options['version'], + $options['passphrase'], + ); + } + elseif (self::LOCATOR_TYPE_FILE_SYSTEM === $locatorType) { + $certificatepath = realpath($options['path']) ?: NULL; + if (NULL === $certificatepath) { + throw new CertificateLocatorException(sprintf('Invalid certificate path %s', $options['path'])); + } + return new FilesystemCertificateLocator($certificatepath, $options['passphrase']); + } + + throw new CertificateLocatorException(sprintf('Invalid certificate locator type: %s', $locatorType)); + } + +} diff --git a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php index 80825dff..d2f2af65 100644 --- a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php +++ b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php @@ -35,6 +35,7 @@ public function __construct( private readonly MeMoHelper $meMoHelper, private readonly ForsendelseHelper $forsendelseHelper, private readonly BeskedfordelerHelper $beskedfordelerHelper, + private readonly CertificateLocatorHelper $certificateLocatorHelper, private readonly LoggerChannelInterface $logger, private readonly LoggerChannelInterface $submissionLogger, private readonly Logger $auditLogger, @@ -60,14 +61,23 @@ public function __construct( */ public function sendDigitalPost(string $type, Message $message, ?ForsendelseI $forsendelse, ?WebformSubmissionInterface $submission = NULL): array { $senderSettings = $this->settings->getSender(); + + if (Settings::PROVIDER_TYPE_FORM === $this->settings->getCertificateProvider()) { + $certificateLocator = $this->certificateLocatorHelper->getCertificateLocator(); + } + else { + $certificateLocator = new KeyCertificateLocator( + $this->settings->getCertificateKey(), + $this->keyHelper + ); + } + $options = [ 'test_mode' => (bool) $this->settings->getTestMode(), 'authority_cvr' => $senderSettings[Settings::SENDER_IDENTIFIER], - 'certificate_locator' => new KeyCertificateLocator( - $this->settings->getCertificateKey(), - $this->keyHelper - ), + 'certificate_locator' => $certificateLocator, ]; + $service = new SF1601($options); $transactionId = Serializer::createUuid(); diff --git a/modules/os2forms_digital_post/src/Helper/Settings.php b/modules/os2forms_digital_post/src/Helper/Settings.php index c0d5384d..176749b2 100644 --- a/modules/os2forms_digital_post/src/Helper/Settings.php +++ b/modules/os2forms_digital_post/src/Helper/Settings.php @@ -23,6 +23,9 @@ final class Settings { public const CERTIFICATE = 'certificate'; public const KEY = 'key'; + public const CERTIFICATE_PROVIDER = 'certificate_provider'; + public const PROVIDER_TYPE_FORM = 'form'; + public const PROVIDER_TYPE_KEY = 'key'; public const PROCESSING = 'processing'; public const QUEUE = 'queue'; @@ -70,6 +73,13 @@ public function getSender(): array { return is_array($value) ? $value : []; } + /** + * Get certificate provider. + */ + public function getCertificateProvider(): ?string { + return $this->get([self::CERTIFICATE, self::CERTIFICATE_PROVIDER]); + } + /** * Get key. */ From b0b898dc3fdcaa35a7e4207acab8bd6668f4134c Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Thu, 23 Jan 2025 14:52:27 +0100 Subject: [PATCH 43/88] Service cleanup using autowire --- .../os2forms_digital_post.services.yml | 54 +++++-------------- .../BeskedfordelerEventSubscriber.php | 2 + .../src/Form/SettingsForm.php | 38 ------------- .../src/Helper/BeskedfordelerHelper.php | 2 + .../src/Helper/DigitalPostHelper.php | 5 ++ .../src/Helper/Settings.php | 2 + .../src/Helper/WebformHelperSF1601.php | 4 ++ 7 files changed, 27 insertions(+), 80 deletions(-) diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 993a7208..ed03ea36 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -8,65 +8,35 @@ services: arguments: [ 'webform_submission' ] Drupal\os2forms_digital_post\Helper\Settings: - arguments: - - "@config.factory" - - "@key.repository" + autowire: true Drupal\os2forms_digital_post\Helper\CertificateLocatorHelper: - arguments: - - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@key.repository" + autowire: true Drupal\os2forms_digital_post\Helper\MeMoHelper: + autowire: true arguments: - - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@plugin.manager.element_info" - - "@webform.token_manager" + $elementInfoManager: "@plugin.manager.element_info" + $webformTokenManager: "@webform.token_manager" Drupal\os2forms_digital_post\Helper\ForsendelseHelper: + autowire: true arguments: - - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@plugin.manager.element_info" - - "@webform.token_manager" + $elementInfoManager: "@plugin.manager.element_info" + $webformTokenManager: "@webform.token_manager" Drupal\os2forms_digital_post\Helper\DigitalPostHelper: - arguments: - - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@Drupal\\os2web_key\\KeyHelper" - - "@plugin.manager.os2web_datalookup" - - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" - - "@Drupal\\os2forms_digital_post\\Helper\\BeskedfordelerHelper" - - "@Drupal\\os2forms_digital_post\\Helper\\CertificateLocatorHelper" - - "@logger.channel.os2forms_digital_post" - - "@logger.channel.os2forms_digital_post_submission" - - "@os2web_audit.logger" + autowire: true Drupal\os2forms_digital_post\Helper\WebformHelperSF1601: - arguments: - - "@Drupal\\os2forms_digital_post\\Helper\\Settings" - - "@entity_type.manager" - - "@plugin.manager.os2web_datalookup" - - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" - - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" - - "@Drupal\\os2forms_digital_post\\Helper\\BeskedfordelerHelper" - - "@logger.channel.os2forms_digital_post" - - "@logger.channel.os2forms_digital_post_submission" - - "@Drupal\\os2forms_digital_post\\Helper\\DigitalPostHelper" + autowire: true Drupal\os2forms_digital_post\Helper\SF1461Helper: Drupal\os2forms_digital_post\EventSubscriber\BeskedfordelerEventSubscriber: - arguments: - - '@Drupal\os2forms_digital_post\Helper\BeskedfordelerHelper' - - '@Drupal\beskedfordeler\Helper\MessageHelper' - - '@Drupal\os2forms_digital_post\Helper\WebformHelperSF1601' - - '@logger.channel.os2forms_digital_post' + autowire: true tags: - { name: 'event_subscriber' } Drupal\os2forms_digital_post\Helper\BeskedfordelerHelper: - arguments: - - '@database' - - '@Drupal\os2forms_digital_post\Helper\MeMoHelper' - - '@logger.channel.os2forms_digital_post' + autowire: true diff --git a/modules/os2forms_digital_post/src/EventSubscriber/BeskedfordelerEventSubscriber.php b/modules/os2forms_digital_post/src/EventSubscriber/BeskedfordelerEventSubscriber.php index cbef5ad4..446dc8f3 100644 --- a/modules/os2forms_digital_post/src/EventSubscriber/BeskedfordelerEventSubscriber.php +++ b/modules/os2forms_digital_post/src/EventSubscriber/BeskedfordelerEventSubscriber.php @@ -8,6 +8,7 @@ use Drupal\os2forms_digital_post\Helper\BeskedfordelerHelper; use Drupal\os2forms_digital_post\Helper\WebformHelperSF1601; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Event subscriber for PostStatusBeskedModtagEvent. @@ -23,6 +24,7 @@ public function __construct( private readonly BeskedfordelerHelper $beskedfordelerHelper, private readonly MessageHelper $messageHelper, private readonly WebformHelperSF1601 $webformHelper, + #[Autowire(service: 'logger.channel.os2forms_digital_post')] LoggerInterface $logger, ) { parent::__construct($logger); diff --git a/modules/os2forms_digital_post/src/Form/SettingsForm.php b/modules/os2forms_digital_post/src/Form/SettingsForm.php index 5bfb04f6..0112a87b 100644 --- a/modules/os2forms_digital_post/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_post/src/Form/SettingsForm.php @@ -35,7 +35,6 @@ public function __construct( ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entityTypeManager, private readonly Settings $settings, - private readonly CertificateLocatorHelper $certificateLocatorHelper, ) { parent::__construct($config_factory); $this->queueStorage = $entityTypeManager->getStorage('advancedqueue_queue'); @@ -51,7 +50,6 @@ public static function create(ContainerInterface $container) { $container->get('config.factory'), $container->get('entity_type.manager'), $container->get(Settings::class), - $container->get(CertificateLocatorHelper::class), ); } @@ -289,15 +287,6 @@ public function buildForm(array $form, FormStateInterface $form_state): array { ), ]; - $form['actions']['testCertificate'] = [ - '#type' => 'submit', - '#name' => 'testCertificate', - '#value' => $this->t('Test certificate'), - '#states' => [ - 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_FORM]], - ], - ]; - return $form; } @@ -307,11 +296,6 @@ public function buildForm(array $form, FormStateInterface $form_state): array { * @phpstan-param array $form */ public function validateForm(array &$form, FormStateInterface $form_state): void { - $triggeringElement = $form_state->getTriggeringElement(); - if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { - return; - } - $values = $form_state->getValues(); if (Settings::PROVIDER_TYPE_FORM === $values[Settings::CERTIFICATE][Settings::CERTIFICATE_PROVIDER]) { @@ -332,12 +316,6 @@ public function validateForm(array &$form, FormStateInterface $form_state): void * @phpstan-param array $form */ public function submitForm(array &$form, FormStateInterface $form_state): void { - $triggeringElement = $form_state->getTriggeringElement(); - if ('testCertificate' === ($triggeringElement['#name'] ?? NULL)) { - $this->testCertificate(); - return; - } - $config = $this->config(Settings::CONFIG_NAME); foreach ([ Settings::TEST_MODE, @@ -376,20 +354,4 @@ private function createDescription(string|array $key, ?TranslatableMarkup $descr return (string) $description; } - /** - * Test certificate. - */ - private function testCertificate(): void { - try { - - $certificateLocator = $this->certificateLocatorHelper->getCertificateLocator(); - $certificateLocator->getCertificates(); - $this->messenger()->addStatus($this->t('Certificate succesfully tested')); - } - catch (\Throwable $throwable) { - $message = $this->t('Error testing certificate: %message', ['%message' => $throwable->getMessage()]); - $this->messenger()->addError($message); - } - } - } diff --git a/modules/os2forms_digital_post/src/Helper/BeskedfordelerHelper.php b/modules/os2forms_digital_post/src/Helper/BeskedfordelerHelper.php index 5b93da5e..c256dc17 100644 --- a/modules/os2forms_digital_post/src/Helper/BeskedfordelerHelper.php +++ b/modules/os2forms_digital_post/src/Helper/BeskedfordelerHelper.php @@ -9,6 +9,7 @@ use Drupal\webform\WebformSubmissionInterface; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Beskedfordeler helper. @@ -24,6 +25,7 @@ class BeskedfordelerHelper { public function __construct( private readonly Connection $database, private readonly MeMoHelper $meMoHelper, + #[Autowire(service: 'logger.channel.os2forms_digital_post')] LoggerInterface $logger, ) { $this->setLogger($logger); diff --git a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php index d2f2af65..44198074 100644 --- a/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php +++ b/modules/os2forms_digital_post/src/Helper/DigitalPostHelper.php @@ -18,6 +18,7 @@ use Oio\Fjernprint\ForsendelseI; use Psr\Log\LoggerInterface; use Psr\Log\LoggerTrait; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Webform helper. @@ -31,13 +32,17 @@ final class DigitalPostHelper implements LoggerInterface { public function __construct( private readonly Settings $settings, private readonly KeyHelper $keyHelper, + #[Autowire(service: 'plugin.manager.os2web_datalookup')] private readonly DataLookupManager $dataLookupManager, private readonly MeMoHelper $meMoHelper, private readonly ForsendelseHelper $forsendelseHelper, private readonly BeskedfordelerHelper $beskedfordelerHelper, private readonly CertificateLocatorHelper $certificateLocatorHelper, + #[Autowire(service: 'logger.channel.os2forms_digital_post')] private readonly LoggerChannelInterface $logger, + #[Autowire(service: 'logger.channel.os2forms_digital_post_submission')] private readonly LoggerChannelInterface $submissionLogger, + #[Autowire(service: 'os2web_audit.logger')] private readonly Logger $auditLogger, ) { } diff --git a/modules/os2forms_digital_post/src/Helper/Settings.php b/modules/os2forms_digital_post/src/Helper/Settings.php index 176749b2..fc7ab385 100644 --- a/modules/os2forms_digital_post/src/Helper/Settings.php +++ b/modules/os2forms_digital_post/src/Helper/Settings.php @@ -7,6 +7,7 @@ use Drupal\Core\Config\ImmutableConfig; use Drupal\key\KeyInterface; use Drupal\key\KeyRepositoryInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * General settings for os2forms_digital_post. @@ -49,6 +50,7 @@ final class Settings { */ public function __construct( ConfigFactoryInterface $configFactory, + #[Autowire(service: 'key.repository')] private readonly KeyRepositoryInterface $keyRepository, ) { $this->runtimeConfig = $configFactory->get(self::CONFIG_NAME); diff --git a/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php b/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php index 3c2c724e..60fae6a2 100644 --- a/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php +++ b/modules/os2forms_digital_post/src/Helper/WebformHelperSF1601.php @@ -21,6 +21,7 @@ use ItkDev\Serviceplatformen\Service\SF1601\SF1601; use Psr\Log\LoggerInterface; use Psr\Log\LoggerTrait; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Webform helper. @@ -51,11 +52,14 @@ final class WebformHelperSF1601 implements LoggerInterface { public function __construct( private readonly Settings $settings, EntityTypeManagerInterface $entityTypeManager, + #[Autowire(service: 'plugin.manager.os2web_datalookup')] private readonly DataLookupManager $dataLookupManager, private readonly MeMoHelper $meMoHelper, private readonly ForsendelseHelper $forsendelseHelper, private readonly BeskedfordelerHelper $beskedfordelerHelper, + #[Autowire(service: 'logger.channel.os2forms_digital_post')] private readonly LoggerChannelInterface $logger, + #[Autowire(service: 'logger.channel.os2forms_digital_post_submission')] private readonly LoggerChannelInterface $submissionLogger, private readonly DigitalPostHelper $digitalPostHelper, ) { From 25f35ce173b582a5b6ca3ac145dd2f7661f00ce7 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 24 Jan 2025 11:05:40 +0100 Subject: [PATCH 44/88] Updates settings forms --- modules/os2forms_digital_post/src/Form/SettingsForm.php | 2 +- modules/os2forms_fasit/src/Form/SettingsForm.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/os2forms_digital_post/src/Form/SettingsForm.php b/modules/os2forms_digital_post/src/Form/SettingsForm.php index 0112a87b..e03f2d8a 100644 --- a/modules/os2forms_digital_post/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_post/src/Form/SettingsForm.php @@ -252,10 +252,10 @@ public function buildForm(array $form, FormStateInterface $form_state): array { '#key_description' => FALSE, '#title' => $this->t('Key'), '#default_value' => $this->settings->getEditableValue([Settings::CERTIFICATE, Settings::PROVIDER_TYPE_KEY]), - '#required' => TRUE, '#description' => $this->createDescription([Settings::CERTIFICATE, Settings::PROVIDER_TYPE_KEY]), '#states' => [ 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_KEY]], + 'required' => [':input[name="certificate[certificate_provider]"]' => ['value' => Settings::PROVIDER_TYPE_KEY]], ], ]; diff --git a/modules/os2forms_fasit/src/Form/SettingsForm.php b/modules/os2forms_fasit/src/Form/SettingsForm.php index fdd6e0e0..a343b307 100644 --- a/modules/os2forms_fasit/src/Form/SettingsForm.php +++ b/modules/os2forms_fasit/src/Form/SettingsForm.php @@ -209,10 +209,10 @@ public function buildForm(array $form, FormStateInterface $form_state): array { 'type' => 'os2web_key_certificate', ], '#title' => $this->t('Key'), - '#required' => TRUE, '#default_value' => $config->get(self::PROVIDER_TYPE_KEY), '#states' => [ 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_KEY]], + 'required' => [':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_KEY]], ], ]; From 3bde9cc5dd064f25c009daf8a34c98a2c3f3c408 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 24 Jan 2025 12:54:52 +0100 Subject: [PATCH 45/88] Updates settings forms --- modules/os2forms_fasit/src/Form/SettingsForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_fasit/src/Form/SettingsForm.php b/modules/os2forms_fasit/src/Form/SettingsForm.php index a343b307..9e05b95b 100644 --- a/modules/os2forms_fasit/src/Form/SettingsForm.php +++ b/modules/os2forms_fasit/src/Form/SettingsForm.php @@ -209,7 +209,7 @@ public function buildForm(array $form, FormStateInterface $form_state): array { 'type' => 'os2web_key_certificate', ], '#title' => $this->t('Key'), - '#default_value' => $config->get(self::PROVIDER_TYPE_KEY), + '#default_value' => $certificateConfig[self::PROVIDER_TYPE_KEY] ?? NULL, '#states' => [ 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_KEY]], 'required' => [':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_KEY]], From e1e38abe6927159b3b24844dcd6f17116d733e4e Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 24 Jan 2025 13:24:29 +0100 Subject: [PATCH 46/88] Updates settings forms --- modules/os2forms_fasit/src/Helper/Settings.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/os2forms_fasit/src/Helper/Settings.php b/modules/os2forms_fasit/src/Helper/Settings.php index 09d0cd92..09410299 100644 --- a/modules/os2forms_fasit/src/Helper/Settings.php +++ b/modules/os2forms_fasit/src/Helper/Settings.php @@ -78,7 +78,9 @@ public function getFasitCertificateLocator(): string { * Get Fasit key certificate configuration. */ public function getFasitCertificateKey(): ?string { - return $this->get(SettingsForm::KEY); + $config = $this->getFasitCertificateConfig(); + + return $config[SettingsForm::PROVIDER_TYPE_KEY] ?? NULL; } /** From 85394521a4551c99746f251c4dc780fb9cbd21c6 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 24 Jan 2025 15:51:21 +0100 Subject: [PATCH 47/88] Update versions requiment --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index aee475b2..4a9bedd8 100644 --- a/composer.json +++ b/composer.json @@ -59,7 +59,7 @@ "itk-dev/serviceplatformen": "^1.5", "mglaman/composer-drupal-lenient": "^1.0", "os2web/os2web_audit": "^0.1.6", - "os2web/os2web_datalookup": "dev-feature/os2web_key as 2.0.0", + "os2web/os2web_datalookup": "^2.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "os2web/os2web_simplesaml": "dev-master", From c6e6cb10c3d579cf8da5f6e9494439e2caacef1a Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 24 Jan 2025 16:00:02 +0100 Subject: [PATCH 48/88] clean up --- composer.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/composer.json b/composer.json index 4a9bedd8..7972ad98 100644 --- a/composer.json +++ b/composer.json @@ -81,10 +81,6 @@ "wsdltophp/packagegenerator": "^4.0" }, "repositories": { - "os2web/os2web_datalookup": { - "type": "vcs", - "url": "https://github.com/itk-dev/os2web_datalookup" - }, "drupal": { "type": "composer", "url": "https://packages.drupal.org/8" From d8424d0c9b9c714fd96d0cd23d07973f88d1431a Mon Sep 17 00:00:00 2001 From: Jeppe Kuhlmann Andersen <78410897+jekuaitk@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:28:11 +0100 Subject: [PATCH 49/88] Update modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php Co-authored-by: Mikkel Ricky --- .../src/Helper/CertificateLocatorHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php index 14710b93..01fde400 100644 --- a/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php +++ b/modules/os2forms_digital_post/src/Helper/CertificateLocatorHelper.php @@ -45,7 +45,7 @@ public function getCertificateLocator(): CertificateLocatorInterface { $locatorType = $certificateSettings['locator_type']; $options = $certificateSettings[$locatorType]; $options += [ - 'passphrase' => $certificateSettings['passphrase'] ?: '', + 'passphrase' => $certificateSettings['passphrase'], ]; if (self::LOCATOR_TYPE_AZURE_KEY_VAULT === $locatorType) { From 243e1e828bdd1f1604f5e5696d17170751043078 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 27 Jan 2025 11:29:28 +0100 Subject: [PATCH 50/88] Autowired services --- .../os2forms_digital_post.services.yml | 6 ------ .../src/Helper/ForsendelseHelper.php | 16 ++++++++++++++++ .../src/Helper/MeMoHelper.php | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index ed03ea36..1b554d91 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -15,15 +15,9 @@ services: Drupal\os2forms_digital_post\Helper\MeMoHelper: autowire: true - arguments: - $elementInfoManager: "@plugin.manager.element_info" - $webformTokenManager: "@webform.token_manager" Drupal\os2forms_digital_post\Helper\ForsendelseHelper: autowire: true - arguments: - $elementInfoManager: "@plugin.manager.element_info" - $webformTokenManager: "@webform.token_manager" Drupal\os2forms_digital_post\Helper\DigitalPostHelper: autowire: true diff --git a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php index 42cbbea1..998c8f40 100644 --- a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php +++ b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php @@ -2,12 +2,14 @@ namespace Drupal\os2forms_digital_post\Helper; +use Drupal\Core\Render\ElementInfoManager; use Drupal\os2forms_digital_post\Exception\InvalidForsendelseException; use Drupal\os2forms_digital_post\Model\Document; use Drupal\os2forms_digital_post\Plugin\WebformHandler\WebformHandlerSF1601; use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult; use Drupal\os2web_datalookup\LookupResult\CprLookupResult; use Drupal\webform\WebformSubmissionInterface; +use Drupal\webform\WebformTokenManagerInterface; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; use Oio\Dkal\AfsendelseModtager; use Oio\Ebxml\CountryIdentificationCode; @@ -16,6 +18,7 @@ use Oio\Fjernprint\ForsendelseModtager; use Oio\Fjernprint\ModtagerAdresse; use Oio\Fjernprint\PostParametre; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Forsendelse helper. @@ -24,6 +27,19 @@ class ForsendelseHelper extends AbstractMessageHelper { // PostKategoriKode. public const POST_KATEGORI_KODE_PRIORITAIRE = 'Prioritaire'; + /** + * {@inheritDoc} + */ + public function __construct( + Settings $settings, + #[Autowire(service: 'plugin.manager.element_info')] + ElementInfoManager $elementInfoManager, + #[Autowire(service: 'webform.token_manager')] + WebformTokenManagerInterface $webformTokenManager, + ) { + parent::__construct($settings, $elementInfoManager, $webformTokenManager); + } + /** * Build forsendelse. */ diff --git a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php index 3ec8b7ed..96b82f48 100644 --- a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php +++ b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php @@ -14,19 +14,35 @@ use DigitalPost\MeMo\MessageHeader; use DigitalPost\MeMo\Recipient; use DigitalPost\MeMo\Sender; +use Drupal\Core\Render\ElementInfoManager; use Drupal\os2forms_digital_post\Model\Document; use Drupal\os2forms_digital_post\Plugin\WebformHandler\WebformHandlerSF1601; use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult; use Drupal\os2web_datalookup\LookupResult\CprLookupResult; use Drupal\webform\WebformSubmissionInterface; +use Drupal\webform\WebformTokenManagerInterface; use ItkDev\Serviceplatformen\Service\SF1601\SF1601; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * MeMo helper. */ class MeMoHelper extends AbstractMessageHelper { + /** + * {@inheritDoc} + */ + public function __construct( + Settings $settings, + #[Autowire(service: 'plugin.manager.element_info')] + ElementInfoManager $elementInfoManager, + #[Autowire(service: 'webform.token_manager')] + WebformTokenManagerInterface $webformTokenManager, + ) { + parent::__construct($settings, $elementInfoManager, $webformTokenManager); + } + /** * Build message. * From 5e19aa507c4807a22c613095f279acb5478fe42b Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 27 Jan 2025 12:55:09 +0100 Subject: [PATCH 51/88] Ignored possible UselessOverridingMethod --- modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php | 3 +++ modules/os2forms_digital_post/src/Helper/MeMoHelper.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php index 998c8f40..a8501ea0 100644 --- a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php +++ b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php @@ -30,6 +30,7 @@ class ForsendelseHelper extends AbstractMessageHelper { /** * {@inheritDoc} */ + // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod public function __construct( Settings $settings, #[Autowire(service: 'plugin.manager.element_info')] @@ -40,6 +41,8 @@ public function __construct( parent::__construct($settings, $elementInfoManager, $webformTokenManager); } + // phpcs:enable + /** * Build forsendelse. */ diff --git a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php index 96b82f48..a88e9df3 100644 --- a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php +++ b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php @@ -33,6 +33,7 @@ class MeMoHelper extends AbstractMessageHelper { /** * {@inheritDoc} */ + // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod public function __construct( Settings $settings, #[Autowire(service: 'plugin.manager.element_info')] @@ -43,6 +44,8 @@ public function __construct( parent::__construct($settings, $elementInfoManager, $webformTokenManager); } + // phpcs:enable + /** * Build message. * From c0c053e34c7ebc84e679ba3e06df7e14122eb566 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 27 Jan 2025 14:54:39 +0100 Subject: [PATCH 52/88] Moved autowire to abstract class --- .../src/Helper/AbstractMessageHelper.php | 2 ++ .../src/Helper/ForsendelseHelper.php | 19 ------------------- .../src/Helper/MeMoHelper.php | 19 ------------------- 3 files changed, 2 insertions(+), 38 deletions(-) diff --git a/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php b/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php index d9fb96a3..e4a85abb 100644 --- a/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php +++ b/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php @@ -12,6 +12,7 @@ use Drupal\webform_attachment\Element\WebformAttachmentBase; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; use Oio\Fjernprint\ForsendelseI; +use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Abstract message helper. @@ -23,6 +24,7 @@ abstract class AbstractMessageHelper { */ public function __construct( readonly protected Settings $settings, + #[Autowire(service: 'plugin.manager.element_info')] readonly protected ElementInfoManager $elementInfoManager, readonly protected WebformTokenManagerInterface $webformTokenManager, ) { diff --git a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php index a8501ea0..42cbbea1 100644 --- a/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php +++ b/modules/os2forms_digital_post/src/Helper/ForsendelseHelper.php @@ -2,14 +2,12 @@ namespace Drupal\os2forms_digital_post\Helper; -use Drupal\Core\Render\ElementInfoManager; use Drupal\os2forms_digital_post\Exception\InvalidForsendelseException; use Drupal\os2forms_digital_post\Model\Document; use Drupal\os2forms_digital_post\Plugin\WebformHandler\WebformHandlerSF1601; use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult; use Drupal\os2web_datalookup\LookupResult\CprLookupResult; use Drupal\webform\WebformSubmissionInterface; -use Drupal\webform\WebformTokenManagerInterface; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; use Oio\Dkal\AfsendelseModtager; use Oio\Ebxml\CountryIdentificationCode; @@ -18,7 +16,6 @@ use Oio\Fjernprint\ForsendelseModtager; use Oio\Fjernprint\ModtagerAdresse; use Oio\Fjernprint\PostParametre; -use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * Forsendelse helper. @@ -27,22 +24,6 @@ class ForsendelseHelper extends AbstractMessageHelper { // PostKategoriKode. public const POST_KATEGORI_KODE_PRIORITAIRE = 'Prioritaire'; - /** - * {@inheritDoc} - */ - // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod - public function __construct( - Settings $settings, - #[Autowire(service: 'plugin.manager.element_info')] - ElementInfoManager $elementInfoManager, - #[Autowire(service: 'webform.token_manager')] - WebformTokenManagerInterface $webformTokenManager, - ) { - parent::__construct($settings, $elementInfoManager, $webformTokenManager); - } - - // phpcs:enable - /** * Build forsendelse. */ diff --git a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php index a88e9df3..3ec8b7ed 100644 --- a/modules/os2forms_digital_post/src/Helper/MeMoHelper.php +++ b/modules/os2forms_digital_post/src/Helper/MeMoHelper.php @@ -14,38 +14,19 @@ use DigitalPost\MeMo\MessageHeader; use DigitalPost\MeMo\Recipient; use DigitalPost\MeMo\Sender; -use Drupal\Core\Render\ElementInfoManager; use Drupal\os2forms_digital_post\Model\Document; use Drupal\os2forms_digital_post\Plugin\WebformHandler\WebformHandlerSF1601; use Drupal\os2web_datalookup\LookupResult\CompanyLookupResult; use Drupal\os2web_datalookup\LookupResult\CprLookupResult; use Drupal\webform\WebformSubmissionInterface; -use Drupal\webform\WebformTokenManagerInterface; use ItkDev\Serviceplatformen\Service\SF1601\SF1601; use ItkDev\Serviceplatformen\Service\SF1601\Serializer; -use Symfony\Component\DependencyInjection\Attribute\Autowire; /** * MeMo helper. */ class MeMoHelper extends AbstractMessageHelper { - /** - * {@inheritDoc} - */ - // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod - public function __construct( - Settings $settings, - #[Autowire(service: 'plugin.manager.element_info')] - ElementInfoManager $elementInfoManager, - #[Autowire(service: 'webform.token_manager')] - WebformTokenManagerInterface $webformTokenManager, - ) { - parent::__construct($settings, $elementInfoManager, $webformTokenManager); - } - - // phpcs:enable - /** * Build message. * From 8d3b2a71afa2db0727b83cb49d02dc40dbb5ebf7 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Thu, 30 Jan 2025 15:16:03 +0200 Subject: [PATCH 53/88] OS-161 Disabling annotation page by default --- .../src/Controller/DigitalSignatureController.php | 6 +++--- .../src/Service/SigningService.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index aaf0979b..9ade1c01 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -68,10 +68,10 @@ public function signCallback($uuid, $hash, $fid = NULL) { /** @var SigningService $signingService */ $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); - $signeFilename = \Drupal::request()->get('file'); - $signedFileContent = $signingService->download($signeFilename); + $signedFilename = \Drupal::request()->get('file'); + $signedFileContent = $signingService->download($signedFilename); if (!$signedFileContent) { - \Drupal::logger('os2forms_digital_signature')->warning('Missing file on remote server %file.', ['%file' => $signeFilename]); + \Drupal::logger('os2forms_digital_signature')->warning('Missing file on remote server %file.', ['%file' => $signedFilename]); throw new NotFoundHttpException(); } diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index ba7a67ee..b38fee7f 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -88,7 +88,7 @@ public function sign(string $document_uri, string $cid, string $forward_url):voi * @return mixed|bool * The binary data of the pdf or FALSE if an error occurred. */ - public function download(string $filename, $leave = FALSE, $annotate = TRUE, $attributes = []) { + public function download(string $filename, $leave = FALSE, $annotate = FALSE, $attributes = []) { if (empty($filename)) { return FALSE; } From 43e5a0497f9703342f3902affd8bbb172c7ce346 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Thu, 30 Jan 2025 15:31:51 +0200 Subject: [PATCH 54/88] OS-144 Fixing cancel digital signature --- .../src/Controller/DigitalSignatureController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 9ade1c01..2ee637c6 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -48,7 +48,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { $webformId = $webformSubmission->getWebform()->id(); // Checking the action - $action = \Drupal::request()->query->get('name'); + $action = \Drupal::request()->query->get('action'); if ($action == 'cancel') { $cancelUrl = $webformSubmission->getWebform()->toUrl()->toString(); From 44d0d3a2a1936735575b56b2bc9bab8f543b1dfa Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 7 Feb 2025 13:13:19 +0100 Subject: [PATCH 55/88] Specified webform.token_manager service --- .../os2forms_digital_post/src/Helper/AbstractMessageHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php b/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php index e4a85abb..c2f2b990 100644 --- a/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php +++ b/modules/os2forms_digital_post/src/Helper/AbstractMessageHelper.php @@ -26,6 +26,7 @@ public function __construct( readonly protected Settings $settings, #[Autowire(service: 'plugin.manager.element_info')] readonly protected ElementInfoManager $elementInfoManager, + #[Autowire(service: 'webform.token_manager')] readonly protected WebformTokenManagerInterface $webformTokenManager, ) { } From 0db0714f9d4323b9cb4d38862ce6d97e71b443f2 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Mon, 17 Feb 2025 17:26:33 +0200 Subject: [PATCH 56/88] OS-167 adding Digital signature validation text --- .../src/Element/AttachmentElement.php | 9 +++- .../src/Os2formsAttachmentPrintBuilder.php | 54 ++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/modules/os2forms_attachment/src/Element/AttachmentElement.php b/modules/os2forms_attachment/src/Element/AttachmentElement.php index 43326d20..8136ea43 100644 --- a/modules/os2forms_attachment/src/Element/AttachmentElement.php +++ b/modules/os2forms_attachment/src/Element/AttachmentElement.php @@ -75,7 +75,14 @@ public static function getFileContent(array $element, WebformSubmissionInterface // Save printable document. $print_engine = $print_engine_manager->createSelectedInstance($element['#export_type']); - $file_path = $print_builder->savePrintable([$webform_submission], $print_engine, $scheme, $file_name); + + // Adding digital signature + if (isset($element['#digital_signature']) && $element['#digital_signature']) { + $file_path = $print_builder->savePrintableDigitalSignature([$webform_submission], $print_engine, $scheme, $file_name); + } + else { + $file_path = $print_builder->savePrintable([$webform_submission], $print_engine, $scheme, $file_name); + } } if ($file_path) { diff --git a/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php b/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php index 67a0c99a..e9d96dd7 100644 --- a/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php +++ b/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php @@ -3,6 +3,9 @@ namespace Drupal\os2forms_attachment; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\File\FileExists; +use Drupal\entity_print\Event\PreSendPrintEvent; +use Drupal\entity_print\Event\PrintEvents; use Drupal\entity_print\Plugin\PrintEngineInterface; use Drupal\entity_print\PrintBuilder; @@ -27,10 +30,56 @@ public function printHtml(EntityInterface $entity, $use_default_css = TRUE, $opt return $renderer->generateHtml([$entity], $render, $use_default_css, $optimize_css); } + /** + * Modified version of the original savePrintable() function. + * + * The only difference is modified call to prepareRenderer with digitalPost flag + * TRUE. + * + * @see PrintBuilder::savePrintable() + * + * @return string + * FALSE or the URI to the file. E.g. public://my-file.pdf. + */ + public function savePrintableDigitalSignature(array $entities, PrintEngineInterface $print_engine, $scheme = 'public', $filename = FALSE, $use_default_css = TRUE) { + $renderer = $this->prepareRenderer($entities, $print_engine, $use_default_css, TRUE); + + // Allow other modules to alter the generated Print object. + $this->dispatcher->dispatch(new PreSendPrintEvent($print_engine, $entities), PrintEvents::PRE_SEND); + + // If we didn't have a URI passed in the generate one. + if (!$filename) { + $filename = $renderer->getFilename($entities) . '.' . $print_engine->getExportType()->getFileExtension(); + } + + $uri = "$scheme://$filename"; + + // Save the file. + return \Drupal::service('file_system')->saveData($print_engine->getBlob(), $uri, FileExists::Replace); + } + /** * {@inheritdoc} */ - protected function prepareRenderer(array $entities, PrintEngineInterface $print_engine, $use_default_css) { + + /** + * Override prepareRenderer() the print engine with the passed entities. + * + * @see PrintBuilder::prepareRenderer + * + * @param array $entities + * An array of entities. + * @param \Drupal\entity_print\Plugin\PrintEngineInterface $print_engine + * The print engine. + * @param bool $use_default_css + * TRUE if we want the default CSS included. + * @param bool $digitalSignature + * If the digital signature message needs to be added. + * + * @return \Drupal\entity_print\Renderer\RendererInterface + * A print renderer. + */ + protected function prepareRenderer(array $entities, PrintEngineInterface $print_engine, $use_default_css, $digitalSignature = false) { if (empty($entities)) { throw new \InvalidArgumentException('You must pass at least 1 entity'); } @@ -50,6 +99,9 @@ protected function prepareRenderer(array $entities, PrintEngineInterface $print_ // structure. That margin is automatically added in PDF and PDF only. $generatedHtml = (string) $renderer->generateHtml($entities, $render, $use_default_css, TRUE); $generatedHtml .= ""; + if ($digitalSignature) { + $generatedHtml .= $this->t('You can validate the signature on this PDF file via validering.nemlog-in.dk.'); + } $print_engine->addPage($generatedHtml); From 6d6af9f613f7d837362d3979cfeb2fa0ec1e4254 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Wed, 23 Apr 2025 13:33:03 +0300 Subject: [PATCH 57/88] Adding module description --- CHANGELOG.md | 1 + .../os2forms_digital_signature.info.yml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb688943..374e91ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] - Updating the display of os2forms package on the status page +- Adding os2forms_digital_signature module ## [4.0.0] 2025-03-06 diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml index d744350b..29547e43 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.info.yml @@ -1,9 +1,9 @@ name: 'OS2Forms Digital Signature' type: module -description: 'todo' +description: 'Provides digital signature functionality' package: 'OS2Forms' core_version_requirement: ^9 || ^10 dependencies: - 'webform:webform' -configure: os2forms_digital_post.admin.settings +configure: os2forms_digital_signature.settings From 6ba43e93682319126ab05fa8b99d869346b25adb Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Thu, 24 Apr 2025 15:33:51 +0200 Subject: [PATCH 58/88] Cleaned up actions and CHANGELOG --- .github/workflows/pr.yml | 6 +++--- CHANGELOG.md | 8 ++------ composer.json | 1 + os2forms.install | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6eb23e77..30b36ac1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -39,7 +39,7 @@ jobs: id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -74,7 +74,7 @@ jobs: id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -107,7 +107,7 @@ jobs: id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} diff --git a/CHANGELOG.md b/CHANGELOG.md index db5d1925..e000ede9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,14 +18,13 @@ before starting to add changes. Use example [placed in the end of the page](#exa - Added support for `os2web_key` in Fasit handler. - Switched from saving settings in key value store to config, i.e the module needs to be reconfigured. -- Removed modules ldap_auth, logging_alerts, maillog - Updating the display of os2forms package on the status page ## [4.0.0] 2025-03-06 -- This is an alias for for 3.22.2. Major change is the module removal, which can lead to lack of backward support. +- This is an alias for 3.22.2. Major change is the module removal, which can lead to lack of backward support. See full release note here: -https://github.com/OS2Forms/os2forms_docs/blob/master/docs/releases/2024-Q4-Release-notes.md +[2024-Q4-Release-notes](https://github.com/OS2Forms/os2forms_docs/blob/master/docs/releases/2024-Q4-Release-notes.md) ## [3.22.2] 2025-02-28 @@ -99,11 +98,8 @@ https://github.com/OS2Forms/os2forms_docs/blob/master/docs/releases/2024-Q4-Rele ## [3.16.0] 2024-08-27 -f/OS-115_dawa_address - [OS-115] Skipping empty maktrikula objects - - [#110](https://github.com/OS2Forms/os2forms/pull/110) - Obsolete module removing - os2forms_consent diff --git a/composer.json b/composer.json index 64d7f962..c01be650 100644 --- a/composer.json +++ b/composer.json @@ -99,6 +99,7 @@ "mglaman/composer-drupal-lenient": true, "phpstan/extension-installer": true, "simplesamlphp/composer-module-installer": true, + "simplesamlphp/composer-xmlprovider-installer": true, "vaimo/composer-patches": true, "zaporylie/composer-drupal-optimizations": true }, diff --git a/os2forms.install b/os2forms.install index 54444281..2079b530 100644 --- a/os2forms.install +++ b/os2forms.install @@ -74,7 +74,7 @@ function os2forms_requirements($phase) { 0 => t('version @version (commit: @reference)', [ '@version' => $version, '@reference' => $reference, - ]) + ]), ], ]; From 9f1015eb253b13040ff4c737ac46f1a825219022 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 25 Apr 2025 08:59:22 +0200 Subject: [PATCH 59/88] Normalized composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c01be650..fba7f905 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "drupal/cache_control_override": "^1.1 || ^2.0", "drupal/clientside_validation": "^4.0", "drupal/coc_forms_auto_export": "^2.0@alpha", - "drupal/config_entity_revisions": "dev-2.0.x", + "drupal/config_entity_revisions": "2.0.x-dev", "drupal/diff": "^1.0", "drupal/embed": "^1.4", "drupal/entity_print": "^2.1", From fc5fc872bbd441e092205eaed68aa39997404df7 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 25 Apr 2025 09:44:38 +0200 Subject: [PATCH 60/88] Removed strict from composer validate --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 30b36ac1..79b1db70 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -47,7 +47,7 @@ jobs: - name: Validate composer files run: | composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true - composer validate --strict composer.json + composer validate composer.json # Check that dependencies resolve. composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Check that composer file is normalized From f9f68e49bc3155f3cfdba218d2d32cc610a83ab2 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 25 Apr 2025 11:10:03 +0200 Subject: [PATCH 61/88] Added comment on why --strict was removed --- .github/workflows/pr.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 79b1db70..c400d61c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -47,6 +47,8 @@ jobs: - name: Validate composer files run: | composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true + # The --strict flag on validate has been removed due to the package drupal/config_entity_revisions 2.0.x-dev + # being considered a version cf. https://getcomposer.org/doc/articles/versions.md#branches composer validate composer.json # Check that dependencies resolve. composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction From b182805871dbbf85907498f8ea95bfbaa23c2421 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 29 Apr 2025 15:55:39 +0200 Subject: [PATCH 62/88] Updated os2web/os2web_datalookup --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 91771568..c3749bde 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "itk-dev/serviceplatformen": "^1.5", "mglaman/composer-drupal-lenient": "^1.0", "os2web/os2web_audit": "^1.0", - "os2web/os2web_datalookup": "^2.0", + "os2web/os2web_datalookup": "dev-feature/os2web_key as 2.1.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "os2web/os2web_simplesaml": "dev-master", @@ -80,6 +80,10 @@ "wsdltophp/packagegenerator": "^4.0" }, "repositories": { + "os2web/os2web_datalookup": { + "type": "vcs", + "url": "https://github.com/itk-dev/os2web_datalookup" + }, "drupal": { "type": "composer", "url": "https://packages.drupal.org/8" From dabb887a6ed492bc5c3d2cdebcaf91d9938ec534 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 30 Apr 2025 10:51:49 +0200 Subject: [PATCH 63/88] Enabled os2web_key module --- os2forms.install | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/os2forms.install b/os2forms.install index 2079b530..b6085142 100644 --- a/os2forms.install +++ b/os2forms.install @@ -228,3 +228,12 @@ function _os2form_install_init_area_terms() { function os2forms_update_103001() { \Drupal::service('module_installer')->install(['os2web_audit']); } + +/** + * Implements hook_update_N(). + * + * Enable os2web_key module. + */ +function os2forms_update_103002() { + \Drupal::service('module_installer')->install(['os2web_key']); +} From 3d44c348a05a44c57a29ab135739ca06a6151941 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 13 May 2025 13:43:49 +0200 Subject: [PATCH 64/88] Cleaned up update hook docblocks --- modules/os2forms_digital_post/os2forms_digital_post.install | 2 +- modules/os2forms_fasit/os2forms_fasit.install | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/os2forms_digital_post/os2forms_digital_post.install b/modules/os2forms_digital_post/os2forms_digital_post.install index f8140579..48768e21 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.install +++ b/modules/os2forms_digital_post/os2forms_digital_post.install @@ -19,7 +19,7 @@ function os2forms_digital_post_schema() { } /** - * Implements hook_update_N(). + * Install OS2Web key module. */ function os2forms_digital_post_update_9001(): void { \Drupal::service('module_installer')->install([ diff --git a/modules/os2forms_fasit/os2forms_fasit.install b/modules/os2forms_fasit/os2forms_fasit.install index b671b660..f862bafb 100644 --- a/modules/os2forms_fasit/os2forms_fasit.install +++ b/modules/os2forms_fasit/os2forms_fasit.install @@ -6,7 +6,7 @@ */ /** - * Implements hook_update_N(). + * Install Key module. */ function os2forms_fasit_update_9001(): void { \Drupal::service('module_installer')->install([ From 1543425c9b23493b6f3aa513f452a047a89bf1bc Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Tue, 13 May 2025 14:58:44 +0200 Subject: [PATCH 65/88] Removed autowiring of services --- .../os2forms_digital_post.services.yml | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/modules/os2forms_digital_post/os2forms_digital_post.services.yml b/modules/os2forms_digital_post/os2forms_digital_post.services.yml index 1b554d91..745b88d2 100644 --- a/modules/os2forms_digital_post/os2forms_digital_post.services.yml +++ b/modules/os2forms_digital_post/os2forms_digital_post.services.yml @@ -8,29 +8,64 @@ services: arguments: [ 'webform_submission' ] Drupal\os2forms_digital_post\Helper\Settings: - autowire: true + arguments: + - "@config.factory" + - "@key.repository" Drupal\os2forms_digital_post\Helper\CertificateLocatorHelper: - autowire: true + arguments: + - "@Drupal\\os2forms_digital_post\\Helper\\Settings" Drupal\os2forms_digital_post\Helper\MeMoHelper: - autowire: true + arguments: + - "@Drupal\\os2forms_digital_post\\Helper\\Settings" + - "@plugin.manager.element_info" + - "@webform.token_manager" Drupal\os2forms_digital_post\Helper\ForsendelseHelper: - autowire: true + arguments: + - "@Drupal\\os2forms_digital_post\\Helper\\Settings" + - "@plugin.manager.element_info" + - "@webform.token_manager" Drupal\os2forms_digital_post\Helper\DigitalPostHelper: - autowire: true + arguments: + - "@Drupal\\os2forms_digital_post\\Helper\\Settings" + - "@Drupal\\os2web_key\\KeyHelper" + - "@plugin.manager.os2web_datalookup" + - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" + - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" + - "@Drupal\\os2forms_digital_post\\Helper\\BeskedfordelerHelper" + - "@Drupal\\os2forms_digital_post\\Helper\\CertificateLocatorHelper" + - "@logger.channel.os2forms_digital_post" + - "@logger.channel.os2forms_digital_post_submission" + - "@os2web_audit.logger" Drupal\os2forms_digital_post\Helper\WebformHelperSF1601: - autowire: true + arguments: + - "@Drupal\\os2forms_digital_post\\Helper\\Settings" + - "@entity_type.manager" + - "@plugin.manager.os2web_datalookup" + - "@Drupal\\os2forms_digital_post\\Helper\\MeMoHelper" + - "@Drupal\\os2forms_digital_post\\Helper\\ForsendelseHelper" + - "@Drupal\\os2forms_digital_post\\Helper\\BeskedfordelerHelper" + - "@logger.channel.os2forms_digital_post" + - "@logger.channel.os2forms_digital_post_submission" + - "@Drupal\\os2forms_digital_post\\Helper\\DigitalPostHelper" Drupal\os2forms_digital_post\Helper\SF1461Helper: Drupal\os2forms_digital_post\EventSubscriber\BeskedfordelerEventSubscriber: - autowire: true + arguments: + - '@Drupal\os2forms_digital_post\Helper\BeskedfordelerHelper' + - '@Drupal\beskedfordeler\Helper\MessageHelper' + - '@Drupal\os2forms_digital_post\Helper\WebformHelperSF1601' + - '@logger.channel.os2forms_digital_post' tags: - { name: 'event_subscriber' } Drupal\os2forms_digital_post\Helper\BeskedfordelerHelper: - autowire: true + arguments: + - '@database' + - '@Drupal\os2forms_digital_post\Helper\MeMoHelper' + - '@logger.channel.os2forms_digital_post' From 65c2b7440f004a37a029905c25fcc0eb74a3b59e Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Mon, 26 May 2025 15:19:25 +0200 Subject: [PATCH 66/88] Removed webmozart/path-util --- CHANGELOG.md | 1 + composer.json | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71752530..0003a740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa - Fix digital post commands - Updated versions in GitHub Actions `uses` steps - Updating the display of os2forms package on the status page +- Remove unused and abandoned package `webmozart/path-util`. ## [4.0.0] 2025-03-06 diff --git a/composer.json b/composer.json index 11ffd686..af820623 100644 --- a/composer.json +++ b/composer.json @@ -75,7 +75,6 @@ "php-http/guzzle7-adapter": "^1.0", "phpoffice/phpword": "^0.18.2", "symfony/options-resolver": "^5.4 || ^6.0", - "webmozart/path-util": "^2.3", "wsdltophp/packagebase": "^5.0", "zaporylie/composer-drupal-optimizations": "^1.2", "mglaman/composer-drupal-lenient": "^1.0" From 2ecaa8275a49b65f8353b8dcbc29fc78fdc514e5 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 28 May 2025 14:45:02 +0200 Subject: [PATCH 67/88] Replaced dependency on os2web_key with proper dependency on key module --- modules/os2forms_fasit/os2forms_fasit.info.yml | 2 +- modules/os2forms_fasit/src/Form/SettingsForm.php | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/os2forms_fasit/os2forms_fasit.info.yml b/modules/os2forms_fasit/os2forms_fasit.info.yml index d151bcde..fe55c94a 100644 --- a/modules/os2forms_fasit/os2forms_fasit.info.yml +++ b/modules/os2forms_fasit/os2forms_fasit.info.yml @@ -6,7 +6,7 @@ core_version_requirement: ^9 || ^10 dependencies: - drupal:webform - drupal:advancedqueue + - key:key - os2forms:os2forms_attachment - os2web:os2web_audit - - os2web_key:os2web_key configure: os2forms_fasit.admin.settings diff --git a/modules/os2forms_fasit/src/Form/SettingsForm.php b/modules/os2forms_fasit/src/Form/SettingsForm.php index 9e05b95b..2cfed651 100644 --- a/modules/os2forms_fasit/src/Form/SettingsForm.php +++ b/modules/os2forms_fasit/src/Form/SettingsForm.php @@ -205,9 +205,6 @@ public function buildForm(array $form, FormStateInterface $form_state): array { $form[self::CERTIFICATE][self::PROVIDER_TYPE_KEY] = [ '#type' => 'key_select', - '#key_filters' => [ - 'type' => 'os2web_key_certificate', - ], '#title' => $this->t('Key'), '#default_value' => $certificateConfig[self::PROVIDER_TYPE_KEY] ?? NULL, '#states' => [ From 266d5deb855a16b22093fd216fbf2503563b3a46 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 28 May 2025 15:04:34 +0200 Subject: [PATCH 68/88] Updated changelog --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7dedde..be5deb3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,18 +11,18 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] -- [PR-168](https://github.com/OS2Forms/os2forms/pull/168) - Cleaned up code -- [PR-166](https://github.com/OS2Forms/os2forms/pull/166) - - Fix digital post commands - - Updated versions in GitHub Actions `uses` steps -- [#101](https://github.com/OS2Forms/os2forms/pull/101) +- [PR-101](https://github.com/OS2Forms/os2forms/pull/101) - Added support for `os2web_key` in Digital post - Switched from saving settings in key value store to config, i.e the module needs to be reconfigured. - Added support for `os2web_key` in Fasit handler. - Switched from saving settings in key value store to config, i.e the module needs to be reconfigured. +- [PR-168](https://github.com/OS2Forms/os2forms/pull/168) + Cleaned up code +- [PR-166](https://github.com/OS2Forms/os2forms/pull/166) + - Fix digital post commands + - Updated versions in GitHub Actions `uses` steps - Updating the display of os2forms package on the status page ## [4.0.0] 2025-03-06 From b7e53b9be7b15b9cbc5351341da74bb35308f595 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Fri, 30 May 2025 14:36:19 +0300 Subject: [PATCH 69/88] OS-110 proper dependency injections --- modules/os2forms_digital_signature/README.md | 2 +- .../os2forms_digital_signature.services.yml | 10 +++- .../Controller/DigitalSignatureController.php | 17 +++++-- .../src/Form/SettingsForm.php | 18 ++++---- .../DigitalSignatureWebformHandler.php | 28 ++++++++--- .../src/Service/SigningService.php | 46 +++++++++++++------ 6 files changed, 89 insertions(+), 32 deletions(-) diff --git a/modules/os2forms_digital_signature/README.md b/modules/os2forms_digital_signature/README.md index 1217d6e9..d7d36d95 100644 --- a/modules/os2forms_digital_signature/README.md +++ b/modules/os2forms_digital_signature/README.md @@ -35,6 +35,6 @@ URL: `admin/os2forms_digital_signature/settings` Must match hash salt on the signature server -- **List IP's which can download unsigned PDF submissions** +- **List IPs which can download unsigned PDF submissions** Only requests from this IP will be able to download PDF which are to be signed. diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml index d5d1b220..f30f3501 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml @@ -1,4 +1,12 @@ services: + logger.channel.os2forms_digital_signature: + parent: logger.channel_base + arguments: [ 'os2forms_digital_signature' ] + os2forms_digital_signature.signing_service: class: Drupal\os2forms_digital_signature\Service\SigningService - arguments: ['@config.factory'] + arguments: + - '@http_client' + - '@config.factory' + - '@entity_type.manager' + - '@logger.channel.os2forms_digital_signature' diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 2ee637c6..90268af2 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -13,6 +13,17 @@ class DigitalSignatureController { + /** + * Logger for channel - os2forms_digital_signature. + * + * @var \Drupal\Core\Logger\LoggerChannelInterface + */ + protected $logger; + + public function __construct() { + $this->logger = \Drupal::logger('os2forms_digital_signature'); + } + /** * Callback for the file being signed. * @@ -71,7 +82,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { $signedFilename = \Drupal::request()->get('file'); $signedFileContent = $signingService->download($signedFilename); if (!$signedFileContent) { - \Drupal::logger('os2forms_digital_signature')->warning('Missing file on remote server %file.', ['%file' => $signedFilename]); + $this->logger->warning('Missing file on remote server %file.', ['%file' => $signedFilename]); throw new NotFoundHttpException(); } @@ -88,7 +99,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { $directory = dirname($expectedFileUri); if (!$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { - \Drupal::logger('os2forms_digital_signature')->error('Failed to prepare directory %directory.', ['%directory' => $directory]); + $this->logger->error('Failed to prepare directory %directory.', ['%directory' => $directory]); } } @@ -106,7 +117,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { } } catch (\Exception $e) { - \Drupal::logger('os2forms_digital_signature')->error('Failed to write to file %uri: @message', ['%uri' => $expectedFileUri, '@message' => $e->getMessage()]); + $this->logger->error('Failed to write to file %uri: @message', ['%uri' => $expectedFileUri, '@message' => $e->getMessage()]); } // Build the URL for the webform submission confirmation page. diff --git a/modules/os2forms_digital_signature/src/Form/SettingsForm.php b/modules/os2forms_digital_signature/src/Form/SettingsForm.php index f40fbffb..5bc855a8 100644 --- a/modules/os2forms_digital_signature/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_signature/src/Form/SettingsForm.php @@ -4,11 +4,13 @@ use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; /** * Digital post settings form. */ class SettingsForm extends ConfigFormBase { + use StringTranslationTrait; /** * Name of the config. @@ -37,27 +39,27 @@ protected function getEditableConfigNames() { public function buildForm(array $form, FormStateInterface $form_state) { $form['os2forms_digital_signature_remove_service_url'] = [ '#type' => 'textfield', - '#title' => t("Signature server URL"), + '#title' => $this->t('Signature server URL'), '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_remove_service_url'), - '#description' => t('E.g. https://signering.bellcom.dk/sign.php?'), + '#description' => $this->t('E.g. https://signering.bellcom.dk/sign.php?'), ]; $form['os2forms_digital_signature_sign_hash_salt'] = [ '#type' => 'textfield', - '#title' => t("Hash Salt used for signature"), + '#title' => $this->t('Hash Salt used for signature'), '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_sign_hash_salt'), - '#description' => t('Must match hash salt on the signature server'), + '#description' => $this->t('Must match hash salt on the signature server'), ]; $form['os2forms_digital_signature_submission_allowed_ips'] = [ '#type' => 'textfield', - '#title' => t("List IP's which can download unsigned PDF submissions"), + '#title' => $this->t('List IPs which can download unsigned PDF submissions'), '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_submission_allowed_ips'), - '#description' => t('Comma separated. Ex. 192.168.1.1,192.168.2.1'), + '#description' => $this->t('Comma separated. e.g. 192.168.1.1,192.168.2.1'), ]; $form['os2forms_digital_signature_submission_retention_period'] = [ '#type' => 'textfield', - '#title' => t('Unsigned submission timespan (s)'), + '#title' => $this->t('Unsigned submission timespan (s)'), '#default_value' => ($this->config(self::$configName)->get('os2forms_digital_signature_submission_retention_period')) ?? 300, - '#description' => t('How many seconds can unsigned submission exist before being automatically deleted'), + '#description' => $this->t('How many seconds can unsigned submission exist before being automatically deleted'), ]; return parent::buildForm($form, $form_state); diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index 2c35b03d..aee3c7ff 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -40,6 +40,21 @@ class DigitalSignatureWebformHandler extends WebformHandlerBase { */ protected $elementManager; + /** + * Logger for channel - os2forms_digital_signature. + * + * @var \Drupal\Core\Logger\LoggerChannelInterface + */ + protected $logger; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->logger = \Drupal::logger('os2forms_digital_signature'); + } + /** * {@inheritdoc} */ @@ -47,6 +62,7 @@ public static function create(ContainerInterface $container, array $configuratio $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->moduleHandler = $container->get('module_handler'); $instance->elementManager = $container->get('plugin.manager.webform.element'); + return $instance; } @@ -62,13 +78,13 @@ public function preSave(WebformSubmissionInterface $webform_submission) { $attachment = $this->getSubmissionAttachment($webform_submission); if (!$attachment) { - \Drupal::logger('os2forms_digital_signature')->error('Attachment cannot be created webform: %webform, webform_submission: %webform_submission', ['%webform' => $webform->id(), '%webform_submission' => $webform_submission->uuid()]); + $this->logger->error('Attachment cannot be created webform: %webform, webform_submission: %webform_submission', ['%webform' => $webform->id(), '%webform_submission' => $webform_submission->uuid()]); return; } $destinationDir = 'private://signing'; if (!\Drupal::service('file_system')->prepareDirectory($destinationDir, FileSystemInterface::CREATE_DIRECTORY)) { - \Drupal::logger('os2forms_digital_signature')->error('File directory cannot be created: %filedirectory', ['%filedirectory' => $destinationDir]); + $this->logger->error('File directory cannot be created: %filedirectory', ['%filedirectory' => $destinationDir]); return; } @@ -80,7 +96,7 @@ public function preSave(WebformSubmissionInterface $webform_submission) { $fileToSign = \Drupal::service('file.repository')->writeData($attachment['filecontent'], $fileUri, FileSystemInterface::EXISTS_REPLACE); } catch (\Exception $e) { - \Drupal::logger('os2forms_digital_signature')->error('File cannot be saved: %fileUri, error: %error', ['%fileUri' => $fileUri, '%error' => $e->getMessage()]); + $this->logger->error('File cannot be saved: %fileUri, error: %error', ['%fileUri' => $fileUri, '%error' => $e->getMessage()]); return; } @@ -94,7 +110,7 @@ public function preSave(WebformSubmissionInterface $webform_submission) { $cid = $signingService->get_cid(); if (empty($cid)) { - \Drupal::logger('os2forms_digital_signature')->error('Failed to obtain cid. Is server running?'); + $this->logger->error('Failed to obtain cid. Is server running?'); return; } @@ -102,8 +118,8 @@ public function preSave(WebformSubmissionInterface $webform_submission) { $salt = \Drupal::service('settings')->get('hash_salt'); $hash = Crypt::hashBase64($webform_submission->uuid() . $webform->id() . $salt); - $attahchmentFid = $attachment['fid'] ?? NULL; - $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash, 'fid' => $attahchmentFid]); + $attachmentFid = $attachment['fid'] ?? NULL; + $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash, 'fid' => $attachmentFid]); // Starting signing, if everything is correct - this funcition will start redirect. $signingService->sign($fileToSignPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index b38fee7f..156ca550 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -4,10 +4,12 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ImmutableConfig; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeManager; +use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\os2forms_digital_signature\Form\SettingsForm; -use Drupal\os2forms_digital_signature\Plugin\WebformHandler\DigitalSignatureWebformHandler; +use GuzzleHttp\ClientInterface; use Symfony\Component\HttpFoundation\RedirectResponse; -use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; class SigningService { @@ -19,8 +21,28 @@ class SigningService { */ private readonly ImmutableConfig $config; - public function __construct(ConfigFactoryInterface $configFactory) { + /** + * Webform storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected EntityStorageInterface $webformStorage; + + /** + * WebformSubmission storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected EntityStorageInterface $webformSubmissionStorage; + + public function __construct( + private readonly ClientInterface $httpClient, + ConfigFactoryInterface $configFactory, + EntityTypeManager $entityTypeManager, + private readonly LoggerChannelInterface $logger) { $this->config = $configFactory->get(SettingsForm::$configName); + $this->webformStorage = $entityTypeManager->getStorage('webform'); + $this->webformSubmissionStorage = $entityTypeManager->getStorage('webform_submission'); } /** @@ -31,9 +53,8 @@ public function __construct(ConfigFactoryInterface $configFactory) { */ public function get_cid() : ?string { $url = $this->config->get('os2forms_digital_signature_remove_service_url') . 'action=getcid'; - $curl = curl_init($url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - $result = curl_exec($curl); + $response = $this->httpClient->request('GET', $url); + $result = $response->getBody()->getContents(); $reply = json_decode($result, JSON_OBJECT_AS_ARRAY); @@ -60,7 +81,7 @@ public function get_cid() : ?string { */ public function sign(string $document_uri, string $cid, string $forward_url):void { if (empty($document_uri) || empty($cid) || empty($forward_url)) { - \Drupal::logger('os2forms_digital_signature')->error('Cannot initiate signing process, check params: document_uri: %document_uri, cid: %cid, forward_url: %forward_url', ['%document_uri' => $document_uri, '%cid' => $cid, '%forward_url' => $forward_url]); + $this->logger->error('Cannot initiate signing process, check params: document_uri: %document_uri, cid: %cid, forward_url: %forward_url', ['%document_uri' => $document_uri, '%cid' => $cid, '%forward_url' => $forward_url]); return; } @@ -98,9 +119,8 @@ public function download(string $filename, $leave = FALSE, $annotate = FALSE, $a $params = ['action' => 'download', 'file' => $filename, 'leave' => $leave, 'annotate' => $annotate, 'attributes' => $attributes]; $url = $this->config->get('os2forms_digital_signature_remove_service_url') . http_build_query($params); - $curl = curl_init($url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - $return = curl_exec($curl); + $response = $this->httpClient->request('GET', $url); + $return = $response->getBody()->getContents(); if (empty($return)) { return FALSE; @@ -149,7 +169,7 @@ public function deleteStalledSubmissions() : void { // Find all with os2forms_digital_signature handlers enabled. foreach ($handler_webform_ids as $webform_id) { - $webform = Webform::load($webform_id); + $webform = $this->webformStorage->load($webform_id); if (!$webform) { continue; } @@ -172,7 +192,7 @@ public function deleteStalledSubmissions() : void { // Find all stalled webform submissions of digital signature forms. $retention_period = ($this->config->get('os2forms_digital_signature_submission_retention_period')) ?? 300; $timestamp_threshold = \Drupal::time()->getRequestTime() - $retention_period; - $query = \Drupal::entityQuery('webform_submission') + $query = $this->webformSubmissionStorage->getQuery() ->accessCheck(FALSE) ->condition('webform_id', $digitalSignatureWebforms, 'IN') ->condition('locked', 0) @@ -186,7 +206,7 @@ public function deleteStalledSubmissions() : void { // Deleting all stalled webform submissions. foreach ($submission_ids as $submission_id) { - $submission = WebformSubmission::load($submission_id); + $submission = $this->webformSubmissionStorage->load($submission_id); $submission->delete(); } } From 51706b5bed87cb69b8914b4e7fe795766550c8bb Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Fri, 30 May 2025 14:40:50 +0300 Subject: [PATCH 70/88] OS-110 refactoring, removing comment lines --- .../Plugin/WebformHandler/DigitalSignatureWebformHandler.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index aee3c7ff..f67590f7 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -3,6 +3,7 @@ namespace Drupal\os2forms_digital_signature\Plugin\WebformHandler; use Drupal\Component\Utility\Crypt; +use Drupal\Core\File\FileExists; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Url; use Drupal\file\FileInterface; @@ -93,15 +94,13 @@ public function preSave(WebformSubmissionInterface $webform_submission) { // Save the file data. try { /** @var FileInterface $fileToSign */ - $fileToSign = \Drupal::service('file.repository')->writeData($attachment['filecontent'], $fileUri, FileSystemInterface::EXISTS_REPLACE); + $fileToSign = \Drupal::service('file.repository')->writeData($attachment['filecontent'], $fileUri, FileExists::Replace); } catch (\Exception $e) { $this->logger->error('File cannot be saved: %fileUri, error: %error', ['%fileUri' => $fileUri, '%error' => $e->getMessage()]); return; } - // Set the status to permanent to prevent file deletion on cron. - //$fileToSign->setPermanent(); $fileToSign->save(); $fileToSignPublicUrl = \Drupal::service('file_url_generator')->generateAbsoluteString($fileToSign->getFileUri()); From 9abfcbde7d3d231329438f12c658d641041f7059 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Fri, 30 May 2025 15:18:23 +0300 Subject: [PATCH 71/88] OS-110 phpcs formatting --- modules/os2forms_digital_signature/README.md | 8 +- .../os2forms_digital_signature.module | 7 +- .../os2forms_digital_signature.routing.yml | 1 - .../Controller/DigitalSignatureController.php | 39 ++++++---- .../src/Element/DigitalSignatureDocument.php | 3 +- .../src/Form/SettingsForm.php | 1 + .../DigitalSignatureDocument.php | 3 +- .../DigitalSignatureWebformHandler.php | 54 +++++++++----- .../src/Service/SigningService.php | 74 +++++++++++++------ 9 files changed, 121 insertions(+), 69 deletions(-) diff --git a/modules/os2forms_digital_signature/README.md b/modules/os2forms_digital_signature/README.md index d7d36d95..f9d0a090 100644 --- a/modules/os2forms_digital_signature/README.md +++ b/modules/os2forms_digital_signature/README.md @@ -11,7 +11,8 @@ This module provides functionality for adding digital signature to the webform P 1. Add the OS2forms attachment element to the form. 2. Indicate that the OS2Forms attachment requires a digital signature. 3. Add the Digital Signature Handler to the webform. -4. If the form requires an email handler, ensure the trigger is set to **...when submission is locked** in the handler’s *Additional settings*. +4. If the form requires an email handler, ensure the trigger is set to **...when submission is locked** in the handler’s +*Additional settings*. ### Flow Explained @@ -19,7 +20,8 @@ This module provides functionality for adding digital signature to the webform P 2. The user is redirected to the signature service to provide their signature. 3. After signing, the user is redirected back to the webform solution. 4. The signed PDF is downloaded and stored in Drupal’s private directory. -5. When a submission PDF is requested (e.g., via download link or email), the signed PDF is served instead of generating a new one on the fly. +5. When a submission PDF is requested (e.g., via download link or email), the signed PDF is served instead of generating +a new one on the fly. ## Settings page @@ -29,12 +31,10 @@ URL: `admin/os2forms_digital_signature/settings` The URL of the service providing digital signature. This is the example of a known service https://signering.bellcom.dk/sign.php? - - **Hash Salt used for signature** Must match hash salt on the signature server - - **List IPs which can download unsigned PDF submissions** Only requests from this IP will be able to download PDF which are to be signed. diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.module b/modules/os2forms_digital_signature/os2forms_digital_signature.module index 994aeba3..6e12a210 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.module +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.module @@ -1,5 +1,10 @@ get('settings'); // Checking if the title has not been overridden. - if ($settings['default_submit_button_label'] == $form['actions']['submit']['#value']){ + if ($settings['default_submit_button_label'] == $form['actions']['submit']['#value']) { $form['actions']['submit']['#value'] = t('Sign and submit'); } } diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml index c286ad3b..41dbc321 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.routing.yml @@ -13,4 +13,3 @@ os2forms_digital_signature.settings: _title: 'Digital signature settings' requirements: _permission: 'administer site configuration' - diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 90268af2..8fddc8b9 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -6,11 +6,12 @@ use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Url; use Drupal\file\Entity\File; -use Drupal\os2forms_digital_signature\Service\SigningService; -use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +/** + * Digital Signature Controller. + */ class DigitalSignatureController { /** @@ -29,14 +30,14 @@ public function __construct() { * * Expecting the file name to be coming as GET parameter. * - * @param $uuid + * @param string $uuid * Webform submission UUID. - * @param $hash - * Hash to check if the request is authentic. - * @param $fid - * File to replace (optional). + * @param string $hash + * Hash to check if the request is authentic. + * @param int|null $fid + * File to replace (optional). * - * @return RedirectResponse + * @return \Symfony\Component\HttpFoundation\RedirectResponse * Redirect response to form submission confirmation. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException @@ -49,7 +50,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { ->loadByProperties(['uuid' => $uuid]); // Since loadByProperties returns an array, we need to fetch the first item. - /** @var WebformSubmissionInterface $webformSubmission */ + /** @var \Drupal\webform\WebformSubmissionInterface $webformSubmission */ $webformSubmission = $submissions ? reset($submissions) : NULL; if (!$webformSubmission) { // Submission does not exist. @@ -58,7 +59,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { $webformId = $webformSubmission->getWebform()->id(); - // Checking the action + // Checking the action. $action = \Drupal::request()->query->get('action'); if ($action == 'cancel') { $cancelUrl = $webformSubmission->getWebform()->toUrl()->toString(); @@ -76,7 +77,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { throw new NotFoundHttpException(); } - /** @var SigningService $signingService */ + /** @var \Drupal\os2forms_digital_signature\Service\SigningService $signingService */ $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); $signedFilename = \Drupal::request()->get('file'); @@ -86,14 +87,16 @@ public function signCallback($uuid, $hash, $fid = NULL) { throw new NotFoundHttpException(); } - /** @var FileSystemInterface $file_system */ + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ $file_system = \Drupal::service('file_system'); - // If $fid is present - we are replacing uploaded/managed file, otherwise creating a new one. + // If $fid is present - we are replacing uploaded/managed file, otherwise + // creating a new one. if ($fid) { $file = File::load($fid); $expectedFileUri = $file->getFileUri(); - } else { + } + else { // Prepare the directory to ensure it exists and is writable. $expectedFileUri = "private://webform/$webformId/digital_signature/$uuid.pdf"; $directory = dirname($expectedFileUri); @@ -105,7 +108,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { // Write the data to the file using Drupal's file system service. try { - $file_system->saveData($signedFileContent, $expectedFileUri , FileSystemInterface::EXISTS_REPLACE); + $file_system->saveData($signedFileContent, $expectedFileUri, FileSystemInterface::EXISTS_REPLACE); // Updating webform submission. $webformSubmission->setLocked(TRUE); @@ -117,7 +120,11 @@ public function signCallback($uuid, $hash, $fid = NULL) { } } catch (\Exception $e) { - $this->logger->error('Failed to write to file %uri: @message', ['%uri' => $expectedFileUri, '@message' => $e->getMessage()]); + $this->logger->error('Failed to write to file %uri: @message', + [ + '%uri' => $expectedFileUri, + '@message' => $e->getMessage(), + ]); } // Build the URL for the webform submission confirmation page. diff --git a/modules/os2forms_digital_signature/src/Element/DigitalSignatureDocument.php b/modules/os2forms_digital_signature/src/Element/DigitalSignatureDocument.php index 5f27ff79..7ac75c97 100644 --- a/modules/os2forms_digital_signature/src/Element/DigitalSignatureDocument.php +++ b/modules/os2forms_digital_signature/src/Element/DigitalSignatureDocument.php @@ -2,11 +2,10 @@ namespace Drupal\os2forms_digital_signature\Element; - use Drupal\webform\Element\WebformManagedFileBase; /** - * Provides a webform element for an 'os2forms_digital_signature_document' element. + * Provides a element for an 'os2forms_digital_signature_document' element. * * @FormElement("os2forms_digital_signature_document") */ diff --git a/modules/os2forms_digital_signature/src/Form/SettingsForm.php b/modules/os2forms_digital_signature/src/Form/SettingsForm.php index 5bc855a8..cc21436a 100644 --- a/modules/os2forms_digital_signature/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_signature/src/Form/SettingsForm.php @@ -79,4 +79,5 @@ public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); } + } diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php b/modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php index 843a63c9..dcf727d8 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformElement/DigitalSignatureDocument.php @@ -33,11 +33,10 @@ public function getItemFormats() { /** * {@inheritdoc} */ - protected function getFileExtensions(array $element = NULL) { + protected function getFileExtensions(?array $element = NULL) { return 'pdf'; } - /** * {@inheritdoc} */ diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index f67590f7..b67cfa98 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -6,14 +6,12 @@ use Drupal\Core\File\FileExists; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Url; -use Drupal\file\FileInterface; -use Drupal\os2forms_digital_signature\Service\SigningService; use Drupal\webform\Plugin\WebformHandlerBase; use Drupal\webform\WebformSubmissionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Webform submission debug handler. + * Digital signature webform handler. * * @WebformHandler( * id = "os2forms_digital_signature", @@ -68,8 +66,8 @@ public static function create(ContainerInterface $container, array $configuratio } /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function preSave(WebformSubmissionInterface $webform_submission) { $webform = $webform_submission->getWebform(); @@ -79,7 +77,12 @@ public function preSave(WebformSubmissionInterface $webform_submission) { $attachment = $this->getSubmissionAttachment($webform_submission); if (!$attachment) { - $this->logger->error('Attachment cannot be created webform: %webform, webform_submission: %webform_submission', ['%webform' => $webform->id(), '%webform_submission' => $webform_submission->uuid()]); + $this->logger->error('Attachment cannot be created webform: %webform, webform_submission: %webform_submission', + [ + '%webform' => $webform->id(), + '%webform_submission' => $webform_submission->uuid(), + ] + ); return; } @@ -89,25 +92,29 @@ public function preSave(WebformSubmissionInterface $webform_submission) { return; } - $fileUri = $destinationDir . '/' . $webform_submission->uuid() .'.pdf'; + $fileUri = $destinationDir . '/' . $webform_submission->uuid() . '.pdf'; // Save the file data. try { - /** @var FileInterface $fileToSign */ + /** @var \Drupal\file\FileInterface $fileToSign */ $fileToSign = \Drupal::service('file.repository')->writeData($attachment['filecontent'], $fileUri, FileExists::Replace); } catch (\Exception $e) { - $this->logger->error('File cannot be saved: %fileUri, error: %error', ['%fileUri' => $fileUri, '%error' => $e->getMessage()]); + $this->logger->error('File cannot be saved: %fileUri, error: %error', + [ + '%fileUri' => $fileUri, + '%error' => $e->getMessage(), + ]); return; } $fileToSign->save(); $fileToSignPublicUrl = \Drupal::service('file_url_generator')->generateAbsoluteString($fileToSign->getFileUri()); - /** @var SigningService $signingService */ + /** @var \Drupal\os2forms_digital_signature\Service\SigningService $signingService */ $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); - $cid = $signingService->get_cid(); + $cid = $signingService->getCid(); if (empty($cid)) { $this->logger->error('Failed to obtain cid. Is server running?'); return; @@ -118,9 +125,16 @@ public function preSave(WebformSubmissionInterface $webform_submission) { $hash = Crypt::hashBase64($webform_submission->uuid() . $webform->id() . $salt); $attachmentFid = $attachment['fid'] ?? NULL; - $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', ['uuid' => $webform_submission->uuid(), 'hash' => $hash, 'fid' => $attachmentFid]); - - // Starting signing, if everything is correct - this funcition will start redirect. + $signatureCallbackUrl = Url::fromRoute('os2forms_digital_signature.sign_callback', + [ + 'uuid' => $webform_submission->uuid(), + 'hash' => $hash, + 'fid' => $attachmentFid, + ] + ); + + // Starting signing, if everything is correct - this funcition will start + // redirect. $signingService->sign($fileToSignPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); } @@ -128,10 +142,11 @@ public function preSave(WebformSubmissionInterface $webform_submission) { * Get OS2forms file attachment. * * @param \Drupal\webform\WebformSubmissionInterface $webform_submission - * A webform submission. + * A webform submission. * * @return array|null - * Array of attachment data. + * Array of attachment data. + * * @throws \Exception */ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_submission) { @@ -140,8 +155,8 @@ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_s // Getting all element types that are added to the webform. // - // Priority is the following: check for os2forms_digital_signature_document, is not found try serving - // os2forms_attachment + // Priority is the following: check for os2forms_digital_signature_document, + // is not found try serving os2forms_attachment. $elementTypes = array_column($this->getWebform()->getElementsDecodedAndFlattened(), '#type'); $attachmentType = ''; if (in_array('os2forms_digital_signature_document', $elementTypes)) { @@ -154,7 +169,8 @@ protected function getSubmissionAttachment(WebformSubmissionInterface $webform_s $elements = $this->getWebform()->getElementsInitializedAndFlattened(); $element_attachments = $this->getWebform()->getElementsAttachments(); foreach ($element_attachments as $element_attachment) { - // Check if the element attachment key is excluded and should not attach any files. + // Check if the element attachment key is excluded and should not attach + // any files. if (isset($this->configuration['excluded_elements'][$element_attachment])) { continue; } diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index 156ca550..17073f2a 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -10,8 +10,10 @@ use Drupal\os2forms_digital_signature\Form\SettingsForm; use GuzzleHttp\ClientInterface; use Symfony\Component\HttpFoundation\RedirectResponse; -use Drupal\webform\Entity\WebformSubmission; +/** + * Digital signing service. + */ class SigningService { /** @@ -39,7 +41,8 @@ public function __construct( private readonly ClientInterface $httpClient, ConfigFactoryInterface $configFactory, EntityTypeManager $entityTypeManager, - private readonly LoggerChannelInterface $logger) { + private readonly LoggerChannelInterface $logger, + ) { $this->config = $configFactory->get(SettingsForm::$configName); $this->webformStorage = $entityTypeManager->getStorage('webform'); $this->webformSubmissionStorage = $entityTypeManager->getStorage('webform_submission'); @@ -48,10 +51,10 @@ public function __construct( /** * Fetch a new cid. * - * @return string|NULL + * @return string|null * The correlation id. */ - public function get_cid() : ?string { + public function getCid() : ?string { $url = $this->config->get('os2forms_digital_signature_remove_service_url') . 'action=getcid'; $response = $this->httpClient->request('GET', $url); $result = $response->getBody()->getContents(); @@ -64,29 +67,41 @@ public function get_cid() : ?string { /** * Sign the document. * - * Signing is done by redirecting the user's browser to a url on the signing server that takes the user - * through the signing flow. + * Signing is done by redirecting the user's browser to a url on the signing + * server that takes the user through the signing flow. * * This function will never return. * * @param string $document_uri - * A uri to a file on the local server that we want to sign or the file name on the signing server in the SIGN_PDF_UPLOAD_DIR. - * In case of a local file, it must be prefixed by 'http://' or 'https://' and be readable from the signing server. + * A uri to a file on the local server that we want to sign or the file name + * on the signing server in the SIGN_PDF_UPLOAD_DIR. + * In case of a local file, it must be prefixed by 'http://' or 'https://' + * and be readable from the signing server. * @param string $cid - * The cid made available by the get_cid() function. + * The cid made available by the getCid() function. * @param string $forward_url * The url on the local server to forward user to afterwards. - * - * @return void */ public function sign(string $document_uri, string $cid, string $forward_url):void { if (empty($document_uri) || empty($cid) || empty($forward_url)) { - $this->logger->error('Cannot initiate signing process, check params: document_uri: %document_uri, cid: %cid, forward_url: %forward_url', ['%document_uri' => $document_uri, '%cid' => $cid, '%forward_url' => $forward_url]); + $this->logger->error('Cannot initiate signing process, check params: document_uri: %document_uri, cid: %cid, forward_url: %forward_url', + [ + '%document_uri' => $document_uri, + '%cid' => $cid, + '%forward_url' => $forward_url, + ] + ); return; } $hash = $this->getHash($forward_url); - $params = ['action' => 'sign', 'cid' => $cid, 'hash' => $hash, 'uri' => base64_encode($document_uri), 'forward_url' => base64_encode($forward_url)]; + $params = [ + 'action' => 'sign', + 'cid' => $cid, + 'hash' => $hash, + 'uri' => base64_encode($document_uri), + 'forward_url' => base64_encode($forward_url), + ]; $url = $this->config->get('os2forms_digital_signature_remove_service_url') . http_build_query($params); $response = new RedirectResponse($url); @@ -98,13 +113,18 @@ public function sign(string $document_uri, string $cid, string $forward_url):voi * * @param string $filename * The filename as given by the signing server. - * @param boolean $leave - * If TRUE, leave the file on the remote server, default is to remove the file after download. - * @param boolean $annotate - * If TRUE, download a pdf with an annotation page. + * @param bool $leave + * If TRUE, leave the file on the remote server, default is to remove the + * file after download. + * @param bool $annotate + * If TRUE, download a pdf with an annotation page. * @param array $attributes - * An array of pairs of prompts and values that will be added to the annotation box, e.g., - * ['IP' => $_SERVER['REMOTE_ADDR'], 'Region' => 'Capital Region Copenhagen']. + * An array of pairs of prompts and values that will be added to the + * annotation box, e.g. + * [ + * 'IP' => $_SERVER['REMOTE_ADDR'], + * 'Region' => 'Capital Region Copenhagen' + * ]. * * @return mixed|bool * The binary data of the pdf or FALSE if an error occurred. @@ -116,7 +136,13 @@ public function download(string $filename, $leave = FALSE, $annotate = FALSE, $a if (!preg_match('/^[a-f0-9]{32}\.pdf$/', $filename)) { return FALSE; } - $params = ['action' => 'download', 'file' => $filename, 'leave' => $leave, 'annotate' => $annotate, 'attributes' => $attributes]; + $params = [ + 'action' => 'download', + 'file' => $filename, + 'leave' => $leave, + 'annotate' => $annotate, + 'attributes' => $attributes, + ]; $url = $this->config->get('os2forms_digital_signature_remove_service_url') . http_build_query($params); $response = $this->httpClient->request('GET', $url); @@ -135,7 +161,7 @@ public function download(string $filename, $leave = FALSE, $annotate = FALSE, $a /** * Calculate the hash value. * - * @param string $name + * @param string $value * The value to hash including salt. * * @return string @@ -149,8 +175,8 @@ private function getHash(string $value) : string { /** * Deletes stalled webform submissions that were left unsigned. * - * Only checked the webforms that have digital_signature handler enabled and the submission is older that a specified - * period. + * Only checked the webforms that have digital_signature handler enabled and + * the submission is older that a specified period. * * @throws \Drupal\Core\Entity\EntityStorageException */ @@ -159,7 +185,7 @@ public function deleteStalledSubmissions() : void { // Finding webforms that have any handler. $query = \Drupal::entityQuery('webform') - ->exists('handlers'); // Only webforms with handlers configured. + ->exists('handlers'); $handler_webform_ids = $query->execute(); // No webforms with handlers, aborting. From 31300811dcc7f6b9386deaf791c3b87ad8a902dc Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Fri, 30 May 2025 16:39:07 +0300 Subject: [PATCH 72/88] OS-110 phpcs formatting --- .../src/Element/AttachmentElement.php | 3 +-- .../src/Os2formsAttachmentPrintBuilder.php | 10 +++++----- modules/os2forms_digital_signature/README.md | 2 +- .../os2forms_digital_signature.services.yml | 1 + .../src/Service/SigningService.php | 6 ++++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/os2forms_attachment/src/Element/AttachmentElement.php b/modules/os2forms_attachment/src/Element/AttachmentElement.php index 8136ea43..cdf3ae44 100644 --- a/modules/os2forms_attachment/src/Element/AttachmentElement.php +++ b/modules/os2forms_attachment/src/Element/AttachmentElement.php @@ -2,7 +2,6 @@ namespace Drupal\os2forms_attachment\Element; -use Drupal\Core\File\FileSystemInterface; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform_attachment\Element\WebformAttachmentBase; @@ -76,7 +75,7 @@ public static function getFileContent(array $element, WebformSubmissionInterface // Save printable document. $print_engine = $print_engine_manager->createSelectedInstance($element['#export_type']); - // Adding digital signature + // Adding digital signature. if (isset($element['#digital_signature']) && $element['#digital_signature']) { $file_path = $print_builder->savePrintableDigitalSignature([$webform_submission], $print_engine, $scheme, $file_name); } diff --git a/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php b/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php index e9d96dd7..a394da3f 100644 --- a/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php +++ b/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php @@ -33,8 +33,8 @@ public function printHtml(EntityInterface $entity, $use_default_css = TRUE, $opt /** * Modified version of the original savePrintable() function. * - * The only difference is modified call to prepareRenderer with digitalPost flag - * TRUE. + * The only difference is modified call to prepareRenderer with digitalPost + * flag TRUE. * * @see PrintBuilder::savePrintable() * @@ -65,8 +65,6 @@ public function savePrintableDigitalSignature(array $entities, PrintEngineInterf /** * Override prepareRenderer() the print engine with the passed entities. * - * @see PrintBuilder::prepareRenderer - * * @param array $entities * An array of entities. * @param \Drupal\entity_print\Plugin\PrintEngineInterface $print_engine @@ -78,8 +76,10 @@ public function savePrintableDigitalSignature(array $entities, PrintEngineInterf * * @return \Drupal\entity_print\Renderer\RendererInterface * A print renderer. + * + * @see PrintBuilder::prepareRenderer */ - protected function prepareRenderer(array $entities, PrintEngineInterface $print_engine, $use_default_css, $digitalSignature = false) { + protected function prepareRenderer(array $entities, PrintEngineInterface $print_engine, $use_default_css, $digitalSignature = FALSE) { if (empty($entities)) { throw new \InvalidArgumentException('You must pass at least 1 entity'); } diff --git a/modules/os2forms_digital_signature/README.md b/modules/os2forms_digital_signature/README.md index f9d0a090..242fa541 100644 --- a/modules/os2forms_digital_signature/README.md +++ b/modules/os2forms_digital_signature/README.md @@ -29,7 +29,7 @@ URL: `admin/os2forms_digital_signature/settings` - **Signature server URL** - The URL of the service providing digital signature. This is the example of a known service https://signering.bellcom.dk/sign.php? + The URL of the service providing digital signature. This is the example of a known service [https://signering.bellcom.dk/sign.php?](https://signering.bellcom.dk/sign.php?) - **Hash Salt used for signature** diff --git a/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml b/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml index f30f3501..33848830 100644 --- a/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml +++ b/modules/os2forms_digital_signature/os2forms_digital_signature.services.yml @@ -7,6 +7,7 @@ services: class: Drupal\os2forms_digital_signature\Service\SigningService arguments: - '@http_client' + - '@datetime.time' - '@config.factory' - '@entity_type.manager' - '@logger.channel.os2forms_digital_signature' diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index 17073f2a..9eafa80d 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -2,6 +2,7 @@ namespace Drupal\os2forms_digital_signature\Service; +use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Entity\EntityStorageInterface; @@ -39,6 +40,7 @@ class SigningService { public function __construct( private readonly ClientInterface $httpClient, + private readonly TimeInterface $time, ConfigFactoryInterface $configFactory, EntityTypeManager $entityTypeManager, private readonly LoggerChannelInterface $logger, @@ -184,7 +186,7 @@ public function deleteStalledSubmissions() : void { $digitalSignatureWebforms = []; // Finding webforms that have any handler. - $query = \Drupal::entityQuery('webform') + $query = $this->webformStorage->getQuery() ->exists('handlers'); $handler_webform_ids = $query->execute(); @@ -217,7 +219,7 @@ public function deleteStalledSubmissions() : void { // Find all stalled webform submissions of digital signature forms. $retention_period = ($this->config->get('os2forms_digital_signature_submission_retention_period')) ?? 300; - $timestamp_threshold = \Drupal::time()->getRequestTime() - $retention_period; + $timestamp_threshold = $this->time->getRequestTime() - $retention_period; $query = $this->webformSubmissionStorage->getQuery() ->accessCheck(FALSE) ->condition('webform_id', $digitalSignatureWebforms, 'IN') From 9ae977f98fe71065860c639b62e04cff9d2edaf7 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Mon, 2 Jun 2025 16:46:51 +0300 Subject: [PATCH 73/88] OS-110 injecting dependency --- .../os2forms_attachment.services.yml | 2 +- .../src/Os2formsAttachmentPrintBuilder.php | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/os2forms_attachment/os2forms_attachment.services.yml b/modules/os2forms_attachment/os2forms_attachment.services.yml index 34d2d676..e477d46f 100644 --- a/modules/os2forms_attachment/os2forms_attachment.services.yml +++ b/modules/os2forms_attachment/os2forms_attachment.services.yml @@ -1,4 +1,4 @@ services: os2forms_attachment.print_builder: class: Drupal\os2forms_attachment\Os2formsAttachmentPrintBuilder - arguments: ['@entity_print.renderer_factory', '@event_dispatcher', '@string_translation'] + arguments: ['@entity_print.renderer_factory', '@event_dispatcher', '@string_translation', '@file_system'] diff --git a/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php b/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php index a394da3f..1f8a9f38 100644 --- a/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php +++ b/modules/os2forms_attachment/src/Os2formsAttachmentPrintBuilder.php @@ -4,16 +4,27 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\File\FileExists; +use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\entity_print\Event\PreSendPrintEvent; use Drupal\entity_print\Event\PrintEvents; use Drupal\entity_print\Plugin\PrintEngineInterface; use Drupal\entity_print\PrintBuilder; +use Drupal\entity_print\Renderer\RendererFactoryInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * The OS2Forms attachment print builder service. */ class Os2formsAttachmentPrintBuilder extends PrintBuilder { + /** + * {@inheritdoc} + */ + public function __construct(RendererFactoryInterface $renderer_factory, EventDispatcherInterface $event_dispatcher, TranslationInterface $string_translation, protected readonly FileSystemInterface $file_system) { + parent::__construct($renderer_factory, $event_dispatcher, $string_translation); + } + /** * {@inheritdoc} */ @@ -55,7 +66,7 @@ public function savePrintableDigitalSignature(array $entities, PrintEngineInterf $uri = "$scheme://$filename"; // Save the file. - return \Drupal::service('file_system')->saveData($print_engine->getBlob(), $uri, FileExists::Replace); + return $this->file_system->saveData($print_engine->getBlob(), $uri, FileExists::Replace); } /** From e01f0c1c1261495dd735f3a11a4626274d2d4c06 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Mon, 9 Jun 2025 12:36:52 +0300 Subject: [PATCH 74/88] OS-110 refactoring --- .../src/Form/SettingsForm.php | 4 +- .../DigitalSignatureWebformHandler.php | 74 ++++++++++++++----- .../src/Service/SigningService.php | 27 +++++-- 3 files changed, 79 insertions(+), 26 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Form/SettingsForm.php b/modules/os2forms_digital_signature/src/Form/SettingsForm.php index cc21436a..a05ec886 100644 --- a/modules/os2forms_digital_signature/src/Form/SettingsForm.php +++ b/modules/os2forms_digital_signature/src/Form/SettingsForm.php @@ -37,10 +37,10 @@ protected function getEditableConfigNames() { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - $form['os2forms_digital_signature_remove_service_url'] = [ + $form['os2forms_digital_signature_remote_service_url'] = [ '#type' => 'textfield', '#title' => $this->t('Signature server URL'), - '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_remove_service_url'), + '#default_value' => $this->config(self::$configName)->get('os2forms_digital_signature_remote_service_url'), '#description' => $this->t('E.g. https://signering.bellcom.dk/sign.php?'), ]; $form['os2forms_digital_signature_sign_hash_salt'] = [ diff --git a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php index b67cfa98..9a616bfc 100644 --- a/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php +++ b/modules/os2forms_digital_signature/src/Plugin/WebformHandler/DigitalSignatureWebformHandler.php @@ -3,11 +3,18 @@ namespace Drupal\os2forms_digital_signature\Plugin\WebformHandler; use Drupal\Component\Utility\Crypt; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\File\FileExists; use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\File\FileUrlGeneratorInterface; +use Drupal\Core\Site\Settings; use Drupal\Core\Url; +use Drupal\file\FileRepositoryInterface; +use Drupal\os2forms_digital_signature\Service\SigningService; +use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\Plugin\WebformHandlerBase; use Drupal\webform\WebformSubmissionInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -30,29 +37,56 @@ class DigitalSignatureWebformHandler extends WebformHandlerBase { * * @var \Drupal\Core\Extension\ModuleHandlerInterface */ - protected $moduleHandler; + private readonly ModuleHandlerInterface $moduleHandler; /** * The webform element plugin manager. * * @var \Drupal\webform\Plugin\WebformElementManagerInterface */ - protected $elementManager; + private readonly WebformElementManagerInterface $elementManager; /** * Logger for channel - os2forms_digital_signature. * - * @var \Drupal\Core\Logger\LoggerChannelInterface + * @var \Psr\Log\LoggerInterface */ - protected $logger; + private readonly LoggerInterface $logger; /** - * {@inheritdoc} + * File system interface. + * + * @var \Drupal\Core\File\FileSystemInterface */ - public function __construct(array $configuration, $plugin_id, $plugin_definition) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->logger = \Drupal::logger('os2forms_digital_signature'); - } + private readonly FileSystemInterface $fileSystem; + + /** + * File repository. + * + * @var \Drupal\file\FileRepositoryInterface + */ + private readonly FileRepositoryInterface $fileRepository; + + /** + * File URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + private readonly FileUrlGeneratorInterface $fileUrlGenerator; + + /** + * OS2Forms signing service. + * + * @var \Drupal\os2forms_digital_signature\Service\SigningService + */ + private readonly SigningService $signingService; + + /** + * Settings service. + * + * @var \Drupal\Core\Site\Settings + */ + private readonly Settings $settings; /** * {@inheritdoc} @@ -61,6 +95,12 @@ public static function create(ContainerInterface $container, array $configuratio $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->moduleHandler = $container->get('module_handler'); $instance->elementManager = $container->get('plugin.manager.webform.element'); + $instance->logger = $container->get('logger.channel.os2forms_digital_signature'); + $instance->fileSystem = $container->get('file_system'); + $instance->fileRepository = $container->get('file.repository'); + $instance->fileUrlGenerator = $container->get('file_url_generator'); + $instance->signingService = $container->get('os2forms_digital_signature.signing_service'); + $instance->settings = $container->get('settings'); return $instance; } @@ -87,7 +127,7 @@ public function preSave(WebformSubmissionInterface $webform_submission) { } $destinationDir = 'private://signing'; - if (!\Drupal::service('file_system')->prepareDirectory($destinationDir, FileSystemInterface::CREATE_DIRECTORY)) { + if (!$this->fileSystem->prepareDirectory($destinationDir, FileSystemInterface::CREATE_DIRECTORY)) { $this->logger->error('File directory cannot be created: %filedirectory', ['%filedirectory' => $destinationDir]); return; } @@ -96,8 +136,7 @@ public function preSave(WebformSubmissionInterface $webform_submission) { // Save the file data. try { - /** @var \Drupal\file\FileInterface $fileToSign */ - $fileToSign = \Drupal::service('file.repository')->writeData($attachment['filecontent'], $fileUri, FileExists::Replace); + $fileToSign = $this->fileRepository->writeData($attachment['filecontent'], $fileUri, FileExists::Replace); } catch (\Exception $e) { $this->logger->error('File cannot be saved: %fileUri, error: %error', @@ -109,19 +148,16 @@ public function preSave(WebformSubmissionInterface $webform_submission) { } $fileToSign->save(); - $fileToSignPublicUrl = \Drupal::service('file_url_generator')->generateAbsoluteString($fileToSign->getFileUri()); - - /** @var \Drupal\os2forms_digital_signature\Service\SigningService $signingService */ - $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); + $fileToSignPublicUrl = $this->fileUrlGenerator->generateAbsoluteString($fileToSign->getFileUri()); - $cid = $signingService->getCid(); + $cid = $this->signingService->getCid(); if (empty($cid)) { $this->logger->error('Failed to obtain cid. Is server running?'); return; } // Creating hash. - $salt = \Drupal::service('settings')->get('hash_salt'); + $salt = $this->settings->get('hash_salt'); $hash = Crypt::hashBase64($webform_submission->uuid() . $webform->id() . $salt); $attachmentFid = $attachment['fid'] ?? NULL; @@ -135,7 +171,7 @@ public function preSave(WebformSubmissionInterface $webform_submission) { // Starting signing, if everything is correct - this funcition will start // redirect. - $signingService->sign($fileToSignPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); + $this->signingService->sign($fileToSignPublicUrl, $cid, $signatureCallbackUrl->setAbsolute()->toString()); } /** diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index 9eafa80d..6a305446 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -29,14 +29,14 @@ class SigningService { * * @var \Drupal\Core\Entity\EntityStorageInterface */ - protected EntityStorageInterface $webformStorage; + private readonly EntityStorageInterface $webformStorage; /** * WebformSubmission storage. * * @var \Drupal\Core\Entity\EntityStorageInterface */ - protected EntityStorageInterface $webformSubmissionStorage; + private readonly EntityStorageInterface $webformSubmissionStorage; public function __construct( private readonly ClientInterface $httpClient, @@ -57,7 +57,7 @@ public function __construct( * The correlation id. */ public function getCid() : ?string { - $url = $this->config->get('os2forms_digital_signature_remove_service_url') . 'action=getcid'; + $url = $this->getServiceUrl() . http_build_query(['action' => 'getcid']); $response = $this->httpClient->request('GET', $url); $result = $response->getBody()->getContents(); @@ -104,7 +104,7 @@ public function sign(string $document_uri, string $cid, string $forward_url):voi 'uri' => base64_encode($document_uri), 'forward_url' => base64_encode($forward_url), ]; - $url = $this->config->get('os2forms_digital_signature_remove_service_url') . http_build_query($params); + $url = $this->getServiceUrl() . http_build_query($params); $response = new RedirectResponse($url); $response->send(); @@ -145,7 +145,7 @@ public function download(string $filename, $leave = FALSE, $annotate = FALSE, $a 'annotate' => $annotate, 'attributes' => $attributes, ]; - $url = $this->config->get('os2forms_digital_signature_remove_service_url') . http_build_query($params); + $url = $this->getServiceUrl() . http_build_query($params); $response = $this->httpClient->request('GET', $url); $return = $response->getBody()->getContents(); @@ -239,4 +239,21 @@ public function deleteStalledSubmissions() : void { } } + /** + * Returns Remove service URL. + * + * @return string + * Remote Service URL, if missing '?' or '&', '?' will be added + * automatically. + */ + public function getServiceUrl() : string { + $url = $this->config->get('os2forms_digital_signature_remote_service_url'); + // Handling URL, if it does not end with '?' or '&'. + if (!str_ends_with($url, '?') && !str_ends_with($url, '&')) { + return $url . '?'; + } + + return $url; + } + } From 23fa488b2cd94a9c3f126c28ac1f30be1cb3aa53 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Mon, 9 Jun 2025 12:55:13 +0300 Subject: [PATCH 75/88] OS-110 refactoring, service injections --- .../Controller/DigitalSignatureController.php | 46 ++++++++++++------- .../src/Service/SigningService.php | 2 +- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 8fddc8b9..54a5d30c 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -3,26 +3,44 @@ namespace Drupal\os2forms_digital_signature\Controller; use Drupal\Component\Utility\Crypt; +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\File\FileExists; use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Site\Settings; use Drupal\Core\Url; use Drupal\file\Entity\File; +use Drupal\os2forms_digital_signature\Service\SigningService; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Digital Signature Controller. */ -class DigitalSignatureController { +class DigitalSignatureController extends ControllerBase { /** - * Logger for channel - os2forms_digital_signature. - * - * @var \Drupal\Core\Logger\LoggerChannelInterface + * Constructor. */ - protected $logger; + public function __construct( + private readonly LoggerInterface $logger, + private readonly Settings $settings, + private readonly SigningService $signingService, + private readonly FileSystemInterface $fileSystem, + ) { + } - public function __construct() { - $this->logger = \Drupal::logger('os2forms_digital_signature'); + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('logger.channel.os2forms_digital_signature'), + $container->get('settings'), + $container->get('os2forms_digital_signature.signing_service'), + $container->get('file_system'), + ); } /** @@ -70,26 +88,20 @@ public function signCallback($uuid, $hash, $fid = NULL) { } // Checking hash. - $salt = \Drupal::service('settings')->get('hash_salt'); + $salt = $this->settings->get('hash_salt'); $tmpHash = Crypt::hashBase64($uuid . $webformId . $salt); if ($hash !== $tmpHash) { // Submission exist, but the provided hash is incorrect. throw new NotFoundHttpException(); } - /** @var \Drupal\os2forms_digital_signature\Service\SigningService $signingService */ - $signingService = \Drupal::service('os2forms_digital_signature.signing_service'); - $signedFilename = \Drupal::request()->get('file'); - $signedFileContent = $signingService->download($signedFilename); + $signedFileContent = $this->signingService->download($signedFilename); if (!$signedFileContent) { $this->logger->warning('Missing file on remote server %file.', ['%file' => $signedFilename]); throw new NotFoundHttpException(); } - /** @var \Drupal\Core\File\FileSystemInterface $file_system */ - $file_system = \Drupal::service('file_system'); - // If $fid is present - we are replacing uploaded/managed file, otherwise // creating a new one. if ($fid) { @@ -101,14 +113,14 @@ public function signCallback($uuid, $hash, $fid = NULL) { $expectedFileUri = "private://webform/$webformId/digital_signature/$uuid.pdf"; $directory = dirname($expectedFileUri); - if (!$file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { + if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) { $this->logger->error('Failed to prepare directory %directory.', ['%directory' => $directory]); } } // Write the data to the file using Drupal's file system service. try { - $file_system->saveData($signedFileContent, $expectedFileUri, FileSystemInterface::EXISTS_REPLACE); + $this->fileSystem->saveData($signedFileContent, $expectedFileUri, FileExists::Replace); // Updating webform submission. $webformSubmission->setLocked(TRUE); diff --git a/modules/os2forms_digital_signature/src/Service/SigningService.php b/modules/os2forms_digital_signature/src/Service/SigningService.php index 6a305446..47505243 100644 --- a/modules/os2forms_digital_signature/src/Service/SigningService.php +++ b/modules/os2forms_digital_signature/src/Service/SigningService.php @@ -240,7 +240,7 @@ public function deleteStalledSubmissions() : void { } /** - * Returns Remove service URL. + * Returns Remote signature service URL. * * @return string * Remote Service URL, if missing '?' or '&', '?' will be added From 4681c85dd64bfe118764a2bb1c4d79399bd175d9 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Mon, 9 Jun 2025 13:11:48 +0300 Subject: [PATCH 76/88] OS-110 refactoring, service injections --- .../Controller/DigitalSignatureController.php | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 54a5d30c..7a85318c 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\Crypt; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\File\FileExists; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Site\Settings; @@ -13,6 +14,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -20,6 +22,13 @@ */ class DigitalSignatureController extends ControllerBase { + /** + * File Storage. + * + * @var EntityStorageInterface + */ + protected EntityStorageInterface $fileStorage; + /** * Constructor. */ @@ -28,7 +37,9 @@ public function __construct( private readonly Settings $settings, private readonly SigningService $signingService, private readonly FileSystemInterface $fileSystem, + private readonly RequestStack $requestStack, ) { + $this->fileStorage = $this->entityTypeManager()->getStorage('file'); } /** @@ -40,6 +51,7 @@ public static function create(ContainerInterface $container) { $container->get('settings'), $container->get('os2forms_digital_signature.signing_service'), $container->get('file_system'), + $container->get('request_stack'), ); } @@ -63,7 +75,7 @@ public static function create(ContainerInterface $container) { */ public function signCallback($uuid, $hash, $fid = NULL) { // Load the webform submission entity by UUID. - $submissions = \Drupal::entityTypeManager() + $submissions = $this->entityTypeManager() ->getStorage('webform_submission') ->loadByProperties(['uuid' => $uuid]); @@ -78,7 +90,9 @@ public function signCallback($uuid, $hash, $fid = NULL) { $webformId = $webformSubmission->getWebform()->id(); // Checking the action. - $action = \Drupal::request()->query->get('action'); + $request = $this->requestStack->getCurrentRequest(); + + $action = $request->query->get('action'); if ($action == 'cancel') { $cancelUrl = $webformSubmission->getWebform()->toUrl()->toString(); @@ -95,7 +109,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { throw new NotFoundHttpException(); } - $signedFilename = \Drupal::request()->get('file'); + $signedFilename = $request->get('file'); $signedFileContent = $this->signingService->download($signedFilename); if (!$signedFileContent) { $this->logger->warning('Missing file on remote server %file.', ['%file' => $signedFilename]); @@ -128,7 +142,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { // If file existing, resave the file to update the size and etc. if ($fid) { - File::load($fid)->save(); + $this->fileStorage->load($fid)?->save(); } } catch (\Exception $e) { From 2482846016d421db8bbb17622cf29d3295eb1484 Mon Sep 17 00:00:00 2001 From: Stanislav Kutasevits Date: Mon, 9 Jun 2025 13:14:29 +0300 Subject: [PATCH 77/88] OS-110 refactoring, phpcs --- .../src/Controller/DigitalSignatureController.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php index 7a85318c..656bf7d4 100644 --- a/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php +++ b/modules/os2forms_digital_signature/src/Controller/DigitalSignatureController.php @@ -9,7 +9,6 @@ use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Site\Settings; use Drupal\Core\Url; -use Drupal\file\Entity\File; use Drupal\os2forms_digital_signature\Service\SigningService; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -25,7 +24,7 @@ class DigitalSignatureController extends ControllerBase { /** * File Storage. * - * @var EntityStorageInterface + * @var \Drupal\Core\Entity\EntityStorageInterface */ protected EntityStorageInterface $fileStorage; @@ -119,7 +118,7 @@ public function signCallback($uuid, $hash, $fid = NULL) { // If $fid is present - we are replacing uploaded/managed file, otherwise // creating a new one. if ($fid) { - $file = File::load($fid); + $file = $this->fileStorage->load($fid); $expectedFileUri = $file->getFileUri(); } else { From cfac16874d81b43aebb91a890c24c2aaa65e4ab3 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 10 Jun 2025 11:05:10 +0200 Subject: [PATCH 78/88] Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bdc949a..7be708de 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] +- Remove unused and abandoned package `webmozart/path-util`. + ## [4.1.0] 2025-06-03 - [PR-176](https://github.com/OS2Forms/os2forms/pull/176) @@ -22,7 +24,6 @@ before starting to add changes. Use example [placed in the end of the page](#exa - Fix digital post commands - Updated versions in GitHub Actions `uses` steps - Updating the display of os2forms package on the status page -- Remove unused and abandoned package `webmozart/path-util`. ## [4.0.0] 2025-03-06 From 8f0a49749957052da0d810a2898a0f6f93600956 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Tue, 10 Jun 2025 11:06:41 +0200 Subject: [PATCH 79/88] Updated CHANGELOG entry with link to PR --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be708de..765a9f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] -- Remove unused and abandoned package `webmozart/path-util`. +- [PR-179](https://github.com/OS2Forms/os2forms/pull/179) + Remove unused and abandoned package `webmozart/path-util`. ## [4.1.0] 2025-06-03 From 37b041da3a27a4ac6f00d2833782d19cc05812cd Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 18 Jun 2025 12:25:36 +0200 Subject: [PATCH 80/88] Upgraded os2web/os2web_datalookup --- composer.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/composer.json b/composer.json index f6d9b016..cb61499b 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "itk-dev/serviceplatformen": "^1.5", "mglaman/composer-drupal-lenient": "^1.0", "os2web/os2web_audit": "^1.0", - "os2web/os2web_datalookup": "dev-feature/os2web_key as 2.1.0", + "os2web/os2web_datalookup": "^3.0", "os2web/os2web_key": "^1.0", "os2web/os2web_nemlogin": "^1.0", "os2web/os2web_simplesaml": "dev-master", @@ -79,10 +79,6 @@ "wsdltophp/packagegenerator": "^4.0" }, "repositories": { - "os2web/os2web_datalookup": { - "type": "vcs", - "url": "https://github.com/itk-dev/os2web_datalookup" - }, "drupal": { "type": "composer", "url": "https://packages.drupal.org/8" From 6bce134e0f4e58540b3473ecd129e1adaf08c472 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 18 Jun 2025 13:03:54 +0200 Subject: [PATCH 81/88] Moved os2forms_digital_signature changelog entry --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2942ad0c..7551094e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ before starting to add changes. Use example [placed in the end of the page](#exa - [PR-179](https://github.com/OS2Forms/os2forms/pull/179) Remove unused and abandoned package `webmozart/path-util`. +- [PR-167](https://github.com/OS2Forms/os2forms/pull/167) + Adding os2forms_digital_signature module ## [4.1.0] 2025-06-03 @@ -25,7 +27,6 @@ before starting to add changes. Use example [placed in the end of the page](#exa - Fix digital post commands - Updated versions in GitHub Actions `uses` steps - Updating the display of os2forms package on the status page -- Adding os2forms_digital_signature module ## [4.0.0] 2025-03-06 From f241de30ecbd9753b1a01f4d90aba5c2250ded39 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 26 Aug 2025 13:33:41 +0200 Subject: [PATCH 82/88] Added condition check to Maestro send notification --- modules/os2forms_forloeb/src/MaestroHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/os2forms_forloeb/src/MaestroHelper.php b/modules/os2forms_forloeb/src/MaestroHelper.php index d2dd0c52..0beea889 100644 --- a/modules/os2forms_forloeb/src/MaestroHelper.php +++ b/modules/os2forms_forloeb/src/MaestroHelper.php @@ -239,6 +239,7 @@ private function sendNotification( || $handler->isDisabled() || $handler->isExcluded() || !$handler->isNotificationEnabled($notificationType) + || !$handler->checkConditions($submission) ) { continue; } From b94b012fba82d820c663a56c62b720e96795ead5 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 26 Aug 2025 13:37:24 +0200 Subject: [PATCH 83/88] Updated changelog --- CHANGELOG.md | 2 ++ modules/os2forms_forloeb/CHANGELOG.md | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbaf66e6..1d5c9427 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] + + ## [4.1.0] 2025-06-03 - [PR-176](https://github.com/OS2Forms/os2forms/pull/176) diff --git a/modules/os2forms_forloeb/CHANGELOG.md b/modules/os2forms_forloeb/CHANGELOG.md index 6d569821..cf175e20 100644 --- a/modules/os2forms_forloeb/CHANGELOG.md +++ b/modules/os2forms_forloeb/CHANGELOG.md @@ -10,6 +10,7 @@ before starting to add changes. ## [Unreleased] +- Added condition tp maestro notification submission handler - Remove dependency on webform_migrate module - Implemented `hook_maestro_zero_user_notification` and added *Maestro notification* handler for sending notifications via email or digital post. From 2acc5f0e887d32d14fd7296a8bcf4aa0c57ae986 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 26 Aug 2025 13:38:21 +0200 Subject: [PATCH 84/88] Updated changelog --- CHANGELOG.md | 2 +- modules/os2forms_forloeb/CHANGELOG.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d5c9427..24d37343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] - +- Added condition tp maestro notification submission handler ## [4.1.0] 2025-06-03 diff --git a/modules/os2forms_forloeb/CHANGELOG.md b/modules/os2forms_forloeb/CHANGELOG.md index cf175e20..6d569821 100644 --- a/modules/os2forms_forloeb/CHANGELOG.md +++ b/modules/os2forms_forloeb/CHANGELOG.md @@ -10,7 +10,6 @@ before starting to add changes. ## [Unreleased] -- Added condition tp maestro notification submission handler - Remove dependency on webform_migrate module - Implemented `hook_maestro_zero_user_notification` and added *Maestro notification* handler for sending notifications via email or digital post. From 34d667f429e715102115665e210e6dee0a818c46 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 26 Aug 2025 13:38:52 +0200 Subject: [PATCH 85/88] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24d37343..f2084b71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ before starting to add changes. Use example [placed in the end of the page](#exa ## [Unreleased] -- Added condition tp maestro notification submission handler +- Added condition to maestro notification submission handler ## [4.1.0] 2025-06-03 From db3a495164d696b98c17743b5a928e79567afa2e Mon Sep 17 00:00:00 2001 From: Anna-Lis Berg Date: Fri, 29 Aug 2025 10:34:43 +0200 Subject: [PATCH 86/88] Create opgavebeskrivelse.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kort skabelon til at diskutere bestillinger af diverse opgaver i OS2forms. skabelonen er på dansk da det er hovedsproget i det daglige bruger og kundevendte arbejde --- templates/opgavebeskrivelse.md | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 templates/opgavebeskrivelse.md diff --git a/templates/opgavebeskrivelse.md b/templates/opgavebeskrivelse.md new file mode 100644 index 00000000..9e4d7748 --- /dev/null +++ b/templates/opgavebeskrivelse.md @@ -0,0 +1,36 @@ +--- +name: Opgavebeskrivelse +about: Standard skabelon for definition af opgaver +title: '' +labels: '' +assignees: '' +--- +# {Kort titel, der repræsenterer opgaven, krævet} + +**Opgavebeskrivelse**: {venligst henvis til eventuelle danske ord, der bruges til at definere aktiviteten i omfanget} +**Møde eller møderækkke **: {en eventuel henvisning til mødet, hvor opgaven blev aftalt, inklusive dato og tidspunkt} + +**Opdragsgiver**: {henvis til beslutningsorganet, der er enige om opgaven} + +## Overordnet opgavebeskrivelse +{Beskriv konteksten og problemstillingen, fx i fri form med to til tre sætninger eller i form af en illustrativ historie.} + +## Kontekst og problem at løse +{Beskriv konteksten og problemstillingen, fx i fri form med to til tre sætninger eller i form af en illustrativ historie.} + +## Vigtige milepæle +* {titel på milepæl 1} +* {titel på milepæl 2} +* … + +## Aktiviteter eller under opgaver (sub issues) + +[x] {titel på opgave 1} +[x] {titel på opgave 2} +[x] {titel på opgave 3} +[x] … + +## Accept kriterier +{Beskriv med enkle ord, hvornår opgaven betragtes som fuldført, og angiv eventuelle formelle godkendelseskrav} + +## Risici & konsekvenser, hvis opgaven ikke udføres. From a057080997baaa86a83febe7152112237286dc95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20Kryger=20S=C3=B8rensen?= <139229719+ds-bellcom@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:26:51 +0200 Subject: [PATCH 87/88] Update opgavebeskrivelse.md Minor update to the task description --- templates/opgavebeskrivelse.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/templates/opgavebeskrivelse.md b/templates/opgavebeskrivelse.md index 9e4d7748..5d413e22 100644 --- a/templates/opgavebeskrivelse.md +++ b/templates/opgavebeskrivelse.md @@ -7,10 +7,11 @@ assignees: '' --- # {Kort titel, der repræsenterer opgaven, krævet} -**Opgavebeskrivelse**: {venligst henvis til eventuelle danske ord, der bruges til at definere aktiviteten i omfanget} -**Møde eller møderækkke **: {en eventuel henvisning til mødet, hvor opgaven blev aftalt, inklusive dato og tidspunkt} +**Opgavebeskrivelse**: {venligst henvis til eventuelle danske ord, der bruges til at definere aktiviteten i omfanget.} -**Opdragsgiver**: {henvis til beslutningsorganet, der er enige om opgaven} +**Møde eller møderækkke**: {en eventuel henvisning til mødet, hvor opgaven blev aftalt, inklusive dato og tidspunkt.} + +**Opdragsgiver**: {henvis til beslutningsorganet, der er enige om opgaven.} ## Overordnet opgavebeskrivelse {Beskriv konteksten og problemstillingen, fx i fri form med to til tre sætninger eller i form af en illustrativ historie.} @@ -21,16 +22,14 @@ assignees: '' ## Vigtige milepæle * {titel på milepæl 1} * {titel på milepæl 2} -* … - -## Aktiviteter eller under opgaver (sub issues) -[x] {titel på opgave 1} -[x] {titel på opgave 2} -[x] {titel på opgave 3} -[x] … +## Aktiviteter eller underopgaver (sub-issues) +- [ ] {titel på opgave 1} +- [ ] {titel på opgave 2} +- [ ] {titel på opgave 3} ## Accept kriterier -{Beskriv med enkle ord, hvornår opgaven betragtes som fuldført, og angiv eventuelle formelle godkendelseskrav} +{Beskriv med enkle ord, hvornår opgaven betragtes som fuldført, og angiv eventuelle formelle godkendelseskrav.} ## Risici & konsekvenser, hvis opgaven ikke udføres. +{Beskriv med enkle ord, hvilke risice og konsekvenser det har, hvis opgaven ikke udføres.} From 28cedf191f277afa3abf71a4c40a65bde43cb91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20Kryger=20S=C3=B8rensen?= <139229719+ds-bellcom@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:49:15 +0200 Subject: [PATCH 88/88] Update opgavebeskrivelse.md Minor spelling error in one of the headlines --- templates/opgavebeskrivelse.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/opgavebeskrivelse.md b/templates/opgavebeskrivelse.md index 5d413e22..c58c5d06 100644 --- a/templates/opgavebeskrivelse.md +++ b/templates/opgavebeskrivelse.md @@ -9,7 +9,7 @@ assignees: '' **Opgavebeskrivelse**: {venligst henvis til eventuelle danske ord, der bruges til at definere aktiviteten i omfanget.} -**Møde eller møderækkke**: {en eventuel henvisning til mødet, hvor opgaven blev aftalt, inklusive dato og tidspunkt.} +**Møde eller møderække**: {en eventuel henvisning til mødet, hvor opgaven blev aftalt, inklusive dato og tidspunkt.} **Opdragsgiver**: {henvis til beslutningsorganet, der er enige om opgaven.}