diff --git a/CHANGELOG.md b/CHANGELOG.md index 23821ca..e849be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,10 @@ 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. + - 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 diff --git a/composer.json b/composer.json index 0124496..aee475b 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 9d3b1aa..0e50ffa 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_digital_post/drush.services.yml b/modules/os2forms_digital_post/drush.services.yml index ac0d5f8..7339bc2 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/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 8d7d17c..5e9bdbb 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 4397c83..e70008f 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: + autowire: true arguments: - - "@keyvalue" + $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" - - "@Drupal\\os2forms_fasit\\Helper\\CertificateLocatorHelper" - - "@os2web_audit.logger" + $auditLogger: "@os2web_audit.logger" diff --git a/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php b/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php new file mode 100644 index 0000000..586adc0 --- /dev/null +++ b/modules/os2forms_fasit/src/Drush/Commands/FasitTestCommand.php @@ -0,0 +1,49 @@ +get(FasitHelper::class), + ); + } + + /** + * Test API access. + * + * @command os2forms-fasit:test:api + * @usage os2forms-fasit:test:api --help + */ + public function testApi(): void { + try { + $this->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 d0fe119..fdd6e0e 100644 --- a/modules/os2forms_fasit/src/Form/SettingsForm.php +++ b/modules/os2forms_fasit/src/Form/SettingsForm.php @@ -2,41 +2,65 @@ 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 CERTIFICATE_PROVIDER = 'certificate_provider'; + public const PROVIDER_TYPE_FORM = 'form'; + public const PROVIDER_TYPE_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,57 +75,74 @@ 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(); + $certificateConfig = $config->get(self::CERTIFICATE) ?? []; $form[self::CERTIFICATE] = [ '#type' => 'fieldset', '#title' => $this->t('Certificate'), '#tree' => TRUE, - CertificateLocatorHelper::LOCATOR_TYPE => [ + self::CERTIFICATE_PROVIDER => [ '#type' => 'select', - '#title' => $this->t('Certificate locator type'), + '#title' => $this->t('Provider'), '#options' => [ - CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT => $this->t('Azure key vault'), - CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM => $this->t('File system'), + self::PROVIDER_TYPE_FORM => $this->t('Form'), + self::PROVIDER_TYPE_KEY => $this->t('Key'), ], - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_TYPE] ?? NULL, + '#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[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT]], + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT], + ], ], ]; @@ -118,9 +159,12 @@ public function buildForm(array $form, FormStateInterface $form_state): array { $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, + '#default_value' => $certificateConfig[CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT][$key] ?? NULL, '#states' => [ - 'required' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT]], + 'required' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_AZURE_KEY_VAULT], + ], ], ]; } @@ -129,15 +173,21 @@ public function buildForm(array $form, FormStateInterface $form_state): array { '#type' => 'fieldset', '#title' => $this->t('File system'), '#states' => [ - 'visible' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]], + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + ':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, + '#default_value' => $certificateConfig[CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]['path'] ?? NULL, '#states' => [ - 'required' => [':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM]], + 'required' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + ':input[name="certificate[locator_type]"]' => ['value' => CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM], + ], ], ], ]; @@ -145,20 +195,39 @@ public function buildForm(array $form, FormStateInterface $form_state): array { $form[self::CERTIFICATE][CertificateLocatorHelper::LOCATOR_PASSPHRASE] = [ '#type' => 'textfield', '#title' => $this->t('Passphrase'), - '#default_value' => $certificate[CertificateLocatorHelper::LOCATOR_PASSPHRASE] ?? NULL, + '#default_value' => $certificateConfig[CertificateLocatorHelper::LOCATOR_PASSPHRASE] ?? NULL, + '#states' => [ + 'visible' => [ + ':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_FORM], + ], + ], ]; - $form['actions']['#type'] = 'actions'; - - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Save settings'), + $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::PROVIDER_TYPE_KEY), + '#states' => [ + 'visible' => [':input[name="certificate[certificate_provider]"]' => ['value' => self::PROVIDER_TYPE_KEY]], + ], ]; - $form['actions']['testCertificate'] = [ - '#type' => 'submit', - '#name' => 'testCertificate', - '#value' => $this->t('Test certificate'), + $form['actions']['ping_api'] = [ + '#type' => 'container', + + self::ACTION_PING_API => [ + '#type' => 'submit', + '#name' => self::ACTION_PING_API, + '#value' => $this->t('Ping API'), + ], + + 'message' => [ + '#markup' => $this->t('Note: Pinging the API will use saved config.'), + ], ]; return $form; @@ -169,20 +238,23 @@ 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(); + $values = $form_state->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])); + 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); } /** @@ -190,44 +262,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::CERTIFICATE, + ] 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 3f244d1..4d56a58 100644 --- a/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php +++ b/modules/os2forms_fasit/src/Helper/CertificateLocatorHelper.php @@ -38,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 2bd84d6..12a5c51 100644 --- a/modules/os2forms_fasit/src/Helper/FasitHelper.php +++ b/modules/os2forms_fasit/src/Helper/FasitHelper.php @@ -4,18 +4,21 @@ 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; 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; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Psr7\Utils; +use Psr\Http\Message\ResponseInterface; use Symfony\Component\HttpFoundation\Response; /** @@ -38,6 +41,7 @@ public function __construct( private readonly ClientInterface $client, private readonly EntityTypeManagerInterface $entityTypeManager, private readonly Settings $settings, + private readonly FileSystemInterface $fileSystem, private readonly CertificateLocatorHelper $certificateLocator, private readonly Logger $auditLogger, ) { @@ -206,8 +210,6 @@ private function uploadDocument(array $uploads, string $submissionId, array $han } } - [$certificateOptions, $tempCertFilename] = $this->getCertificateOptionsAndTempCertFilename(); - $body = $doc->saveXML(); if (!$body) { @@ -219,20 +221,14 @@ private function uploadDocument(array $uploads, string $submissionId, array $han 'Content-Type' => 'application/xml', ], 'body' => $body, - 'cert' => $certificateOptions, ]; // 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 +258,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 +321,6 @@ private function uploadFile(string $originalFilename, string $tempFilename, stri self::FASIT_API_METHOD_UPLOAD ); - [$certificateOptions, $tempCertFilename] = $this->getCertificateOptionsAndTempCertFilename(); - // Attempt upload. try { $options = [ @@ -356,18 +330,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 +479,96 @@ 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 { + $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 { + // 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; + } + } + } + + /** + * 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 de065fc..09d0cd9 100644 --- a/modules/os2forms_fasit/src/Helper/Settings.php +++ b/modules/os2forms_fasit/src/Helper/Settings.php @@ -2,115 +2,109 @@ 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 Fasit configuration selector. */ - public function getFasitApiVersion(): string { - return $this->get(SettingsForm::FASIT_API_VERSION, ''); + public function getFasitCertificateConfig(): ?array { + return $this->get(SettingsForm::CERTIFICATE); } /** - * Get certificate. - * - * @phpstan-return array + * Get Fasit certificate provider. */ - public function getCertificate(): array { - $value = $this->get(SettingsForm::CERTIFICATE); - return is_array($value) ? $value : []; + public function getFasitCertificateProvider(): string { + $config = $this->getFasitCertificateConfig(); + + return $config[SettingsForm::CERTIFICATE_PROVIDER] ?? SettingsForm::PROVIDER_TYPE_FORM; } /** - * Get a setting value. - * - * @param string $key - * The key. - * @param mixed|null $default - * The default value. - * - * @return mixed - * The setting value. + * Get Fasit certificate locator. */ - 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 getFasitCertificateLocator(): string { + $config = $this->getFasitCertificateConfig(); - return $this->store->get($key, $default); + return $config[CertificateLocatorHelper::LOCATOR_TYPE] ?? CertificateLocatorHelper::LOCATOR_TYPE_FILE_SYSTEM; } /** - * Set settings. - * - * @throws \Symfony\Component\OptionsResolver\Exception\ExceptionInterface - * - * @phpstan-param array $settings + * Get Fasit key certificate configuration. + */ + public function getFasitCertificateKey(): ?string { + return $this->get(SettingsForm::KEY); + } + + /** + * Get certificate. */ - public function setSettings(array $settings): self { - $settings = $this->getSettingsResolver()->resolve($settings); - foreach ($settings as $key => $value) { - $this->store->set($key, $value); - } + public function getKeyValue(): ?string { + $key = $this->keyRepository->getKey( + $this->getFasitCertificateKey(), + ); - return $this; + return $key?->getKeyValue(); } /** - * Get settings resolver. + * Get a setting value. + * + * @param string $key + * The key. + * @param mixed|null $default + * The default value. + * + * @return mixed + * The setting value. */ - 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; } }