From 7175de031f4fe33f8c8fcaf5b3a851ac658cc3a2 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 1 May 2024 09:13:38 +0200 Subject: [PATCH] Added support for os2web_key --- .gitignore | 2 + composer.json | 7 +- src/Exception/RuntimeException.php | 8 + .../os2web/DataLookup/DataLookupBase.php | 93 ++++++++++- .../os2web/DataLookup/DatafordelerBase.php | 106 +++++++++++-- .../os2web/DataLookup/DatafordelerCVR.php | 8 +- .../os2web/DataLookup/DatafordelerPNumber.php | 8 +- .../DataLookup/ServiceplatformenBase.php | 150 +++++++++++++----- .../DataLookup/ServiceplatformenCPR.php | 6 +- .../ServiceplatformenCPRExtended.php | 4 +- 10 files changed, 320 insertions(+), 72 deletions(-) create mode 100644 .gitignore create mode 100644 src/Exception/RuntimeException.php 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/composer.json b/composer.json index bc478a1..2204b03 100644 --- a/composer.json +++ b/composer.json @@ -6,9 +6,14 @@ "prefer-stable": true, "license": "EUPL-1.2", "require": { - "ext-soap": "*" + "ext-soap": "*", + "os2web/os2web_key": "dev-os2web_key" }, "repositories": { + "os2web/os2web_key": { + "type": "vcs", + "url": "https://github.com/rimi-itk/os2web_key" + }, "drupal": { "type": "composer", "url": "https://packages.drupal.org/8" diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php new file mode 100644 index 0000000..f1b163c --- /dev/null +++ b/src/Exception/RuntimeException.php @@ -0,0 +1,8 @@ +setConfiguration($configuration); + $this->init(); + } + + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) + { + /** @var KeyRepositoryInterface $keyRepository */ + $keyRepository = $container->get('key.repository'); + /** @var FileSystem $fileSystem */ + $fileSystem = $container->get('file_system'); + + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $keyRepository, + $fileSystem + ); } /** + * Plugin init method. + */ + protected function init() + { + } + + /** * {@inheritdoc} */ public function label() { @@ -58,7 +102,10 @@ public function setConfiguration(array $configuration) { * {@inheritdoc} */ public function defaultConfiguration() { - return []; + return [ + 'certificate_provider' => '', + 'certificate_key' => '', + ]; } /** @@ -89,4 +136,44 @@ public function isReady() { return $this->isReady; } + /** + * Get certificate. + */ + protected function getCertificate(): string { + return ''; + } + + /** + * 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, FileSystem::EXISTS_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 1e05287..fc875df 100644 --- a/src/Plugin/os2web/DataLookup/DatafordelerBase.php +++ b/src/Plugin/os2web/DataLookup/DatafordelerBase.php @@ -2,8 +2,11 @@ namespace Drupal\os2web_datalookup\Plugin\os2web\DataLookup; +use Drupal\Core\File\FileSystem; use Drupal\Core\Form\FormStateInterface; +use Drupal\os2web_datalookup\Exception\RuntimeException; use GuzzleHttp\Client; +use Psr\Http\Message\ResponseInterface; /** * Defines base plugin class for Datafordeler lookup plugins. @@ -11,24 +14,28 @@ abstract class DatafordelerBase extends DataLookupBase { /** - * Plugin readiness flag. + * The client. * - * @var bool + * @var Client */ - protected $httpClient; + private $httpClient; /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->init(); + public function defaultConfiguration() + { + return [ + 'cert_path_live' => '', + 'cert_passphrase_live' => '', + ] + parent::defaultConfiguration(); } /** - * Plugin init method. + * {@inheritdoc} */ - private function init() { + protected function init() { + parent::init(); $this->isReady = FALSE; $configuration = $this->getConfiguration(); @@ -40,8 +47,10 @@ private function init() { '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; } @@ -71,18 +80,53 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['webserviceurl_live'], ]; - $form['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, + ], + + 'certificate_key' => [ + '#type' => 'key_select', + '#key_filters' => [ + 'type' => 'os2web_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]], + ], + ], - $form['cert_passphrase_live'] = [ + '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; @@ -104,4 +148,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 (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 4b81f52..56bcac8 100644 --- a/src/Plugin/os2web/DataLookup/DatafordelerCVR.php +++ b/src/Plugin/os2web/DataLookup/DatafordelerCVR.php @@ -24,10 +24,8 @@ class DatafordelerCVR extends DatafordelerBase implements DataLookupInterfaceCom */ public function defaultConfiguration() { return [ - 'webserviceurl_live' => 'https://s5-certservices.datafordeler.dk/CVR/HentCVRData/1/REST/', - 'cert_path_live' => '', - 'cert_passphrase_live' => '', - ]; + 'webserviceurl_live' => 'https://s5-certservices.datafordeler.dk/CVR/HentCVRData/1/REST/', + ] + parent::defaultConfiguration(); } /** @@ -66,7 +64,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s */ public function lookup($cvr) { try { - $response = $this->httpClient->get('hentVirksomhedMedCVRNummer', ['query' => ['pCVRNummer' => $cvr]]); + $response = $this->getResponse('hentVirksomhedMedCVRNummer', ['query' => ['pCVRNummer' => $cvr]]); $result = json_decode((string) $response->getBody()); } catch (ClientException $e) { diff --git a/src/Plugin/os2web/DataLookup/DatafordelerPNumber.php b/src/Plugin/os2web/DataLookup/DatafordelerPNumber.php index 0a8d495..28debb8 100644 --- a/src/Plugin/os2web/DataLookup/DatafordelerPNumber.php +++ b/src/Plugin/os2web/DataLookup/DatafordelerPNumber.php @@ -24,10 +24,8 @@ class DatafordelerPNumber extends DatafordelerBase implements DataLookupInterfac */ public function defaultConfiguration() { return [ - 'webserviceurl_live' => 'https://s5-certservices.datafordeler.dk/CVR/HentCVRData/1/REST/', - 'cert_path_live' => '', - 'cert_passphrase_live' => '', - ]; + 'webserviceurl_live' => 'https://s5-certservices.datafordeler.dk/CVR/HentCVRData/1/REST/', + ] + parent::defaultConfiguration(); } /** @@ -66,7 +64,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s */ public function lookup($param) { try { - $response = $this->httpClient->get('hentProduktionsenhedMedPNummer', ['query' => ['ppNummer' => $param]]); + $response = $this->getResponse('hentProduktionsenhedMedPNummer', ['query' => ['ppNummer' => $param]]); $result = json_decode((string) $response->getBody()); } catch (ClientException $e) { diff --git a/src/Plugin/os2web/DataLookup/ServiceplatformenBase.php b/src/Plugin/os2web/DataLookup/ServiceplatformenBase.php index 51c3291..cad7869 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; /** * Defines base plugin class for Serviceplatformen plugins. @@ -23,32 +26,24 @@ abstract class ServiceplatformenBase extends DataLookupBase { */ protected $client; - /** - * {@inheritdoc} - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->init(); - } - /** * {@inheritdoc} */ public function defaultConfiguration() { return [ - 'mode_selector' => 0, - 'serviceagreementuuid' => '', - 'serviceuuid' => '', - 'wsdl' => '', - 'location' => '', - 'location_test' => '', - 'usersystemuuid' => '', - 'useruuid' => '', - 'accountinginfo' => '', - 'certfile_passphrase' => '', - 'certfile' => '', - 'certfile_test' => '', - ]; + 'mode_selector' => 0, + 'serviceagreementuuid' => '', + 'serviceuuid' => '', + 'wsdl' => '', + 'location' => '', + 'location_test' => '', + 'usersystemuuid' => '', + 'useruuid' => '', + 'accountinginfo' => '', + 'certfile_passphrase' => '', + 'certfile' => '', + 'certfile_test' => '', + ] + parent::defaultConfiguration(); } /** @@ -119,22 +114,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_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; @@ -164,9 +198,10 @@ public function getStatus() { } /** - * Plugin init method. + * {@inheritdoc} */ - private function init() { + protected function init() { + parent::init(); ini_set('soap.wsdl_cache_enabled', 0); ini_set('soap.wsdl_cache_ttl', 0); $this->status = $this->t('Plugin is ready to work')->__toString(); @@ -202,13 +237,19 @@ private function init() { } } + $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; @@ -216,7 +257,8 @@ private function init() { case 1: $ws_config = [ 'location' => $this->configuration['location_test'], - 'local_cert' => $this->configuration['certfile_test'], + 'local_cert' => $this->createLocalCertPath(), + 'passphrase' => $passphrase, 'trace' => TRUE, ]; break; @@ -289,6 +331,7 @@ protected function query($method, array $request) { } try { + $localCertPath = $this->writeCertificateToFile(); $response = (array) $this->client->$method($request); $response['status'] = TRUE; } @@ -297,9 +340,36 @@ protected function query($method, array $request) { 'status' => FALSE, 'error' => $e->faultstring, ]; + } finally { + // Remove temporary certificate file. + if (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(); + } + + $filename = 0 === $this->configuration['mode_selector'] + ? $this->configuration['certfile'] + : $this->configuration['certfile_test']; + + return file_get_contents($filename); + } + } diff --git a/src/Plugin/os2web/DataLookup/ServiceplatformenCPR.php b/src/Plugin/os2web/DataLookup/ServiceplatformenCPR.php index b2e0f24..32114ee 100644 --- a/src/Plugin/os2web/DataLookup/ServiceplatformenCPR.php +++ b/src/Plugin/os2web/DataLookup/ServiceplatformenCPR.php @@ -22,9 +22,9 @@ class ServiceplatformenCPR extends ServiceplatformenBase implements DataLookupIn * {@inheritdoc} */ public function defaultConfiguration() { - return array_merge(parent::defaultConfiguration(), [ - 'test_mode_fixed_cpr' => '', - ]); + return [ + 'test_mode_fixed_cpr' => '', + ] + parent::defaultConfiguration(); } /** diff --git a/src/Plugin/os2web/DataLookup/ServiceplatformenCPRExtended.php b/src/Plugin/os2web/DataLookup/ServiceplatformenCPRExtended.php index d2c7026..38d680e 100644 --- a/src/Plugin/os2web/DataLookup/ServiceplatformenCPRExtended.php +++ b/src/Plugin/os2web/DataLookup/ServiceplatformenCPRExtended.php @@ -53,9 +53,9 @@ class ServiceplatformenCPRExtended extends ServiceplatformenBase implements Data * {@inheritdoc} */ public function defaultConfiguration() { - return array_merge(parent::defaultConfiguration(), [ + return [ 'test_mode_fixed_cpr' => '', - ]); + ] + parent::defaultConfiguration(); } /**