diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 132f08e..547705a 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 @@ -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') }} @@ -49,13 +49,16 @@ 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 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 @@ -70,7 +73,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/.gitignore b/.gitignore new file mode 100644 index 0000000..d8a7996 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor/ diff --git a/.markdownlintrc b/.markdownlintrc new file mode 100644 index 0000000..be08e16 --- /dev/null +++ b/.markdownlintrc @@ -0,0 +1,19 @@ +{ + // @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, + "line_length": 120 + }, + + // 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 0243377..4986648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +* [PR-13](https://github.com/OS2web/os2web_datalookup/pull/13) + Added support for [os2web_key](https://github.com/OS2web/os2web_key) + ## [2.0.4] 2025-01-29 * Ensure postal code is only added to city if `CVRAdresse_postdistrikt` is not set. diff --git a/README.md b/README.md index a84bb1a..ebf5236 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ if ($cprPlugin->isReady()) { ## New services/features -### Datafordeler integration (https://datafordeler.dk) +### Datafordeler integration () In the scope of os2forms project already implemented light integration with Danmarks Adresseregister (DAR) via fetching data for form elements autocomplete. @@ -98,3 +98,37 @@ will be exposed for third persons. To avoid/prevent this behavior, we recommend use `Config ignore` module, where you can add all settings you do not want to export/import via the configuration management system. + +## Coding standards + +Our coding are checked by GitHub Actions (cf. +[.github/workflows/pr.yml](.github/workflows/pr.yml)). Use the commands below to +run the checks locally. + +### PHP + +```shell +docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.3-fpm composer install +# Fix (some) coding standards issues +docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.3-fpm composer coding-standards-apply +# Check that code adheres to the coding standards +docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.3-fpm composer coding-standards-check +``` + +### Markdown + +```shell +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: + +```shell +docker run --rm --volume ${PWD}:/app --workdir /app itkdev/php8.3-fpm ./scripts/code-analysis +``` diff --git a/composer.json b/composer.json index aa34269..fb62caa 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,18 @@ { "name": "os2web/os2web_datalookup", - "type": "drupal-module", "description": "Provides integration with Danish data lookup services such as Service platformen or Datafordeler.", - "minimum-stability": "dev", - "prefer-stable": true, "license": "EUPL-1.2", + "type": "drupal-module", "require": { - "ext-soap": "*" + "ext-soap": "*", + "os2web/os2web_key": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "drupal/coder": "^8.3", + "ergebnis/composer-normalize": "^2.45", + "os2web/os2web_audit": "^0.1", + "phpunit/phpunit": "^9.5" }, "repositories": { "drupal": { @@ -18,29 +24,27 @@ "url": "https://asset-packagist.org" } }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "drupal/coder": "^8.3", - "phpunit/phpunit": "^9.5", - "os2web/os2web_audit": "^0.1" + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "ergebnis/composer-normalize": true + }, + "sort-packages": true }, "scripts": { - "coding-standards-check/phpcs": [ - "phpcs --standard=phpcs.xml.dist" - ], - "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": { - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true - } } } diff --git a/os2web_datalookup.info.yml b/os2web_datalookup.info.yml index c0abace..9454c79 100644 --- a/os2web_datalookup.info.yml +++ b/os2web_datalookup.info.yml @@ -3,5 +3,7 @@ type: module description: 'Provides integration with Danish data lookup services such as Service platformen or Datafordeler.' package: 'OS2web' core_version_requirement: ^8 || ^9 || ^10 + dependencies: - 'os2web:os2web_audit' + - 'os2web_key:os2web_key' diff --git a/os2web_datalookup.install b/os2web_datalookup.install index 55bc8c5..e0119a3 100644 --- a/os2web_datalookup.install +++ b/os2web_datalookup.install @@ -33,3 +33,12 @@ function os2web_datalookup_update_9003(): void { $config->set("pnumber_lookup.default_plugin", 'datafordeler_pnumber'); $config->save(); } + +/** + * Implements hook_update_N(). + */ +function os2web_datalookup_update_9004() { + \Drupal::service('module_installer')->install([ + 'os2web_key', + ], TRUE); +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..e51b69f --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,13 @@ +parameters: + level: 6 + paths: + - src + + ignoreErrors: + - "#Method [a-zA-Z0-9\\_\\\\:\\(\\)]+ has parameter \\$[a-zA-Z0-9_]+ with no value type specified in iterable type array#" + - "#Method [a-zA-Z0-9\\_\\\\:\\(\\)]+ return type has no value type specified in iterable type array#" + - '#Unsafe usage of new static\(\).#' + +# Local Variables: +# mode: yaml +# End: diff --git a/scripts/code-analysis b/scripts/code-analysis new file mode 100755 index 0000000..73ec750 --- /dev/null +++ b/scripts/code-analysis @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +script_dir=$(pwd) +module_name=$(basename "$script_dir") +drupal_dir=vendor/drupal-module-code-analysis +# Relative to $drupal_dir +module_path=web/modules/contrib/$module_name + +cd "$script_dir" || exit + +drupal_composer() { + composer --working-dir="$drupal_dir" --no-interaction "$@" +} + +# # Create new Drupal 9 project +# if [ ! -f "$drupal_dir/composer.json" ]; then +# composer --no-interaction create-project drupal/recommended-project:^9 "$drupal_dir" +# fi +# # Copy our code into the modules folder + +# # 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" + +# # 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 + +# # Allow ALL plugins +# # https://getcomposer.org/doc/06-config.md#allow-plugins +# drupal_composer config --no-plugins allow-plugins true + +# drupal_composer require wikimedia/composer-merge-plugin +# drupal_composer config extra.merge-plugin.include "$module_path/composer.json" +# # https://www.drupal.org/project/drupal/issues/3220043#comment-14845434 +# drupal_composer require --dev symfony/phpunit-bridge + +# Run PHPStan +(cd "$drupal_dir" && vendor/bin/phpstan --configuration="$module_path/phpstan.neon") diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php new file mode 100644 index 0000000..1631731 --- /dev/null +++ b/src/Exception/RuntimeException.php @@ -0,0 +1,10 @@ +auditLogger = $auditLogger; $this->setConfiguration($configuration); } @@ -52,11 +64,20 @@ public function __construct( * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + /** @var \Drupal\os2web_audit\Service\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('os2web_audit.logger'), + $auditLogger, + $keyRepository, + $fileSystem ); } @@ -86,8 +107,11 @@ public function setConfiguration(array $configuration): static { /** * {@inheritdoc} */ - public function defaultConfiguration(): array { - return []; + public function defaultConfiguration() { + return [ + 'certificate_provider' => '', + 'certificate_key' => '', + ]; } /** @@ -118,4 +142,41 @@ public function isReady(): bool { return $this->isReady; } + /** + * Get certificate. + */ + abstract protected function getCertificate(): string; + + /** + * Create a temporary file path for a certificate. + * + * Note: We do not want the create a file. Just get a temporary file name. + * + * @return string + * The local certificate path. + */ + protected function createLocalCertPath(): string { + $this->localCertPath = $this->fileSystem->getTempDirectory() . '/' . uniqid('os2web_datalookup_local_cert_'); + + return $this->localCertPath; + } + + /** + * Write certificate to temporary certificate file. + * + * @return string + * The local certificate path. + */ + protected function writeCertificateToFile(): string { + // Write certificate to local_cert location. + $certificate = $this->getCertificate(); + $localCertPath = $this->localCertPath; + $result = $this->fileSystem->saveData($certificate, $localCertPath, FileExists::Replace); + if (!$result) { + return new RuntimeException(sprintf('Error writing certificate to temporary file %s', $localCertPath)); + } + + return $result; + } + } diff --git a/src/Plugin/os2web/DataLookup/DatafordelerBase.php b/src/Plugin/os2web/DataLookup/DatafordelerBase.php index 9c3179a..4d39fae 100644 --- a/src/Plugin/os2web/DataLookup/DatafordelerBase.php +++ b/src/Plugin/os2web/DataLookup/DatafordelerBase.php @@ -2,9 +2,13 @@ namespace Drupal\os2web_datalookup\Plugin\os2web\DataLookup; +use Drupal\Core\File\FileSystem; use Drupal\Core\Form\FormStateInterface; +use Drupal\key\KeyRepositoryInterface; +use Drupal\os2web_datalookup\Exception\RuntimeException; use Drupal\os2web_audit\Service\Logger; use GuzzleHttp\Client; +use Psr\Http\Message\ResponseInterface; /** * Defines base plugin class for Datafordeler lookup plugins. @@ -26,15 +30,17 @@ public function __construct( $plugin_id, $plugin_definition, Logger $auditLogger, + KeyRepositoryInterface $keyRepository, + FileSystem $fileSystem, ) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $auditLogger); + parent::__construct($configuration, $plugin_id, $plugin_definition, $auditLogger, $keyRepository, $fileSystem); $this->init(); } /** - * Plugin init method. + * {@inheritdoc} */ - private function init(): void { + protected function init(): void { $this->isReady = FALSE; $configuration = $this->getConfiguration(); @@ -46,8 +52,10 @@ private function init(): void { 'accept' => 'application/json', ], ]; - if ($certPath = $configuration['cert_path_live']) { - $options['cert'] = $certPath; + + if (isset($configuration['cert_path_live']) || isset($configuration['key'])) { + $options['cert'] = $this->createLocalCertPath(); + $this->httpClient = new Client($options); $this->isReady = TRUE; } @@ -77,18 +85,53 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['webserviceurl_live'], ]; - $form['cert_path_live'] = [ - '#type' => 'textfield', - '#title' => $this->t('Certificate (LIVE)'), - '#description' => $this->t('Path to the certificate'), - '#default_value' => $this->configuration['cert_path_live'], - ]; + $form['certificate'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Certificate'), + + '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' => $this->configuration['certificate_provider'] ?? self::PROVIDER_TYPE_FORM, + ], - $form['cert_passphrase_live'] = [ - '#type' => 'password', - '#title' => $this->t('Certificate passphrase (LIVE)'), - '#description' => $this->t('leave empty if not used'), - '#default_value' => $this->configuration['cert_passphrase_live'], + 'certificate_key' => [ + '#type' => 'key_select', + '#key_filters' => [ + 'type' => 'os2web_key_certificate', + ], + '#title' => $this->t('Key'), + '#default_value' => $this->configuration['certificate_key'] ?? NULL, + '#states' => [ + 'required' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_KEY]], + 'visible' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_KEY]], + ], + ], + + 'cert_path_live' => [ + '#type' => 'textfield', + '#title' => $this->t('Certificate (LIVE)'), + '#description' => $this->t('Path to the certificate'), + '#default_value' => $this->configuration['cert_path_live'], + '#states' => [ + 'required' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_FORM]], + 'visible' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_FORM]], + ], + ], + + 'cert_passphrase_live' => [ + '#type' => 'password', + '#title' => $this->t('Certificate passphrase (LIVE)'), + '#description' => $this->t('leave empty if not used'), + '#default_value' => $this->configuration['cert_passphrase_live'], + '#states' => [ + 'visible' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_FORM]], + ], + ], ]; return $form; @@ -110,4 +153,40 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s $this->setConfiguration($configuration); } + /** + * Get response. + */ + protected function getResponse(string $uri, array $options): ResponseInterface { + try { + $localCertPath = $this->writeCertificateToFile(); + + return $this->httpClient->get($uri, $options); + } finally { + // Remove temporary certificate file. + if (isset($localCertPath) && file_exists($localCertPath)) { + unlink($localCertPath); + } + } + } + + /** + * Get certificate. + */ + protected function getCertificate(): string { + $provider = $this->configuration['certificate_provider'] ?? NULL; + if (self::PROVIDER_TYPE_KEY === $provider) { + $keyId = $this->configuration['certificate_key'] ?? ''; + $key = $this->keyRepository->getKey($keyId); + if (NULL === $key) { + throw new RuntimeException(sprintf('Cannot get key %s', $keyId)); + } + + return $key->getKeyValue(); + } + + $filename = $this->configuration['cert_path_live']; + + return file_get_contents($filename); + } + } diff --git a/src/Plugin/os2web/DataLookup/DatafordelerCVR.php b/src/Plugin/os2web/DataLookup/DatafordelerCVR.php index 8dfac53..3477e16 100644 --- a/src/Plugin/os2web/DataLookup/DatafordelerCVR.php +++ b/src/Plugin/os2web/DataLookup/DatafordelerCVR.php @@ -25,9 +25,7 @@ class DatafordelerCVR extends DatafordelerBase implements DataLookupCompanyInter public function defaultConfiguration(): array { return [ 'webserviceurl_live' => 'https://s5-certservices.datafordeler.dk/CVR/HentCVRData/1/REST/', - 'cert_path_live' => '', - 'cert_passphrase_live' => '', - ]; + ] + parent::defaultConfiguration(); } /** diff --git a/src/Plugin/os2web/DataLookup/DatafordelerPNumber.php b/src/Plugin/os2web/DataLookup/DatafordelerPNumber.php index 14ac1ba..72a882b 100644 --- a/src/Plugin/os2web/DataLookup/DatafordelerPNumber.php +++ b/src/Plugin/os2web/DataLookup/DatafordelerPNumber.php @@ -25,9 +25,7 @@ class DatafordelerPNumber extends DatafordelerBase implements DataLookupCompanyI public function defaultConfiguration(): array { return [ 'webserviceurl_live' => 'https://s5-certservices.datafordeler.dk/CVR/HentCVRData/1/REST/', - 'cert_path_live' => '', - 'cert_passphrase_live' => '', - ]; + ] + parent::defaultConfiguration(); } /** diff --git a/src/Plugin/os2web/DataLookup/ServiceplatformenBase.php b/src/Plugin/os2web/DataLookup/ServiceplatformenBase.php index a888d73..959fd30 100644 --- a/src/Plugin/os2web/DataLookup/ServiceplatformenBase.php +++ b/src/Plugin/os2web/DataLookup/ServiceplatformenBase.php @@ -2,7 +2,10 @@ namespace Drupal\os2web_datalookup\Plugin\os2web\DataLookup; +use Drupal\Core\File\FileSystem; use Drupal\Core\Form\FormStateInterface; +use Drupal\key\KeyRepositoryInterface; +use Drupal\os2web_datalookup\Exception\RuntimeException; use Drupal\os2web_audit\Service\Logger; /** @@ -32,8 +35,10 @@ public function __construct( $plugin_id, $plugin_definition, Logger $auditLogger, + KeyRepositoryInterface $keyRepository, + FileSystem $fileSystem, ) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $auditLogger); + parent::__construct($configuration, $plugin_id, $plugin_definition, $auditLogger, $keyRepository, $fileSystem); $this->init(); } @@ -54,7 +59,7 @@ public function defaultConfiguration(): array { 'certfile_passphrase' => '', 'certfile' => '', 'certfile_test' => '', - ]; + ] + parent::defaultConfiguration(); } /** @@ -125,22 +130,61 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['accountinginfo'], ]; - $form['certfile_passphrase'] = [ - '#type' => 'password', - '#title' => 'Certfile passphrase', - '#default_value' => $this->configuration['certfile_passphrase'], - ]; + $form['certificate'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Certificate'), + + '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' => $this->configuration['certificate_provider'] ?? self::PROVIDER_TYPE_FORM, + ], - $form['certfile'] = [ - '#type' => 'textfield', - '#title' => 'Certfile (live)', - '#default_value' => $this->configuration['certfile'], - ]; + 'certificate_key' => [ + '#type' => 'key_select', + '#key_filters' => [ + 'type' => 'os2web_key_certificate', + ], + '#title' => $this->t('Key'), + '#default_value' => $this->configuration['certificate_key'] ?? NULL, + '#states' => [ + 'required' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_KEY]], + 'visible' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_KEY]], + ], + ], - $form['certfile_test'] = [ - '#type' => 'textfield', - '#title' => 'Certfile (test)', - '#default_value' => $this->configuration['certfile_test'], + 'certfile_passphrase' => [ + '#type' => 'password', + '#title' => 'Certfile passphrase', + '#default_value' => $this->configuration['certfile_passphrase'], + '#states' => [ + 'visible' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_FORM]], + ], + ], + + 'certfile' => [ + '#type' => 'textfield', + '#title' => 'Certfile (live)', + '#default_value' => $this->configuration['certfile'], + '#states' => [ + 'required' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_FORM]], + 'visible' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_FORM]], + ], + ], + + 'certfile_test' => [ + '#type' => 'textfield', + '#title' => 'Certfile (test)', + '#default_value' => $this->configuration['certfile_test'], + '#states' => [ + 'required' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_FORM]], + 'visible' => [':input[name="certificate_provider"]' => ['value' => self::PROVIDER_TYPE_FORM]], + ], + ], ]; return $form; @@ -170,7 +214,7 @@ public function getStatus(): string { } /** - * Plugin init method. + * {@inheritdoc} */ private function init(): void { ini_set('soap.wsdl_cache_enabled', 0); @@ -209,13 +253,19 @@ private function init(): void { } } + $provider = $this->configuration['certificate_provider'] ?? NULL; + $passphrase = self::PROVIDER_TYPE_KEY === $provider + // The certificate provider provides a passwordless certificate. + ? '' + : ($this->configuration['certfile_passphrase'] ?? ''); + try { switch ($this->configuration['mode_selector']) { case 0: $ws_config = [ 'location' => $this->configuration['location'], - 'local_cert' => $this->configuration['certfile'], - 'passphrase' => $this->configuration['certfile_passphrase'], + 'local_cert' => $this->createLocalCertPath(), + 'passphrase' => $passphrase, 'trace' => TRUE, ]; break; @@ -223,7 +273,8 @@ private function init(): void { case 1: $ws_config = [ 'location' => $this->configuration['location_test'], - 'local_cert' => $this->configuration['certfile_test'], + 'local_cert' => $this->createLocalCertPath(), + 'passphrase' => $passphrase, 'trace' => TRUE, ]; break; @@ -309,6 +360,7 @@ protected function query(string $method, array $request): array { } try { + $localCertPath = $this->writeCertificateToFile(); $msg = sprintf('Method %s called with (%s)', $method, $auditLoggingMethodParameter); $this->auditLogger->info('DataLookup', $msg); $response = (array) $this->client->$method($request); @@ -321,9 +373,38 @@ protected function query(string $method, array $request): array { 'status' => FALSE, 'error' => $e->faultstring, ]; + } finally { + // Remove temporary certificate file. + if (isset($localCertPath) && file_exists($localCertPath)) { + unlink($localCertPath); + } } return $response; } + /** + * Get certificate. + */ + protected function getCertificate(): string { + $provider = $this->configuration['certificate_provider'] ?? NULL; + if (self::PROVIDER_TYPE_KEY === $provider) { + $keyId = $this->configuration['certificate_key'] ?? ''; + $key = $this->keyRepository->getKey($keyId); + if (NULL === $key) { + throw new RuntimeException(sprintf('Cannot get key %s', $keyId)); + } + + return $key->getKeyValue(); + } + + // Get certificate filename based on whether we're running in prod mode + // (mode_selector = 0) or test mode. + $filename = 0 === $this->configuration['mode_selector'] + ? $this->configuration['certfile'] + : $this->configuration['certfile_test']; + + return file_get_contents($filename); + } + }