From 79887ee7e65ec12b1c0b19442344834de2fd42cb Mon Sep 17 00:00:00 2001 From: Robert Nasarek Date: Mon, 7 Aug 2023 15:47:35 +0200 Subject: [PATCH] add config page and validation --- README.md | 20 +++- .../WisskiCloudAccountManagerCreateForm.php | 12 +- .../WisskiCloudAccountManagerSettingsForm.php | 43 ++++--- ...skiCloudAccountManagerDaemonApiActions.php | 105 ++++++++++++++++-- wisski_cloud_account_manager.info.yml | 2 +- wisski_cloud_account_manager.links.menu.yml | 5 + wisski_cloud_account_manager.module | 44 ++++++++ wisski_cloud_account_manager.routing.yml | 13 ++- wisski_cloud_account_manager.services.yml | 2 + 9 files changed, 210 insertions(+), 36 deletions(-) create mode 100644 wisski_cloud_account_manager.links.menu.yml create mode 100644 wisski_cloud_account_manager.module diff --git a/README.md b/README.md index 8155ca1..5e1b800 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ -# WissKI Cloud Account Manager \ No newline at end of file +# WissKI Cloud Account Manager + +## Introduction +This module provides an account creation page at `/cloud-account-manager/create` which can be used to create a new account. +It is intended to be used in combination with the [WissKI Cloud API Daemon](). +If you submit the account form, an email with a validation link is sent to the email address you provided. After the validation, the provision of your WissKI Cloud instance at .wisski.cloud starts and can be checked at `/cloud-account-manager/check/`. +## Requirements +* A correct configured and functional [WissKI Cloud API Daemon](). If you use docker, be sure to have the daemon and drupal site in the same network. +* A valid SMTP System - you may need additional modules for SMTP, i.e. [SMTP Authentication Support](https://www.drupal.org/project/smtp). +## Installation +- Clone this repository into your modules folder and enable the module. +- Enable the module `WissKI Cloud Account Manager` in the Drupal UI. + +## Configuration +- Go to `/admin/config/system/cloud-account-manager` and configure the module. + +## Maintainers + +## License diff --git a/src/Form/WisskiCloudAccountManagerCreateForm.php b/src/Form/WisskiCloudAccountManagerCreateForm.php index f9abd75..447220c 100644 --- a/src/Form/WisskiCloudAccountManagerCreateForm.php +++ b/src/Form/WisskiCloudAccountManagerCreateForm.php @@ -51,7 +51,7 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - $form['personname'] = [ + $form['personName'] = [ '#type' => 'textfield', '#title' => $this->t('Person name'), '#description' => $this->t('Your first and last name.'), @@ -117,7 +117,7 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { /** * {@inheritdoc} */ - public function validateForm(array &$form, FormStateInterface $form_state) { + public function validateForm(array &$form, FormStateInterface $form_state): void { // Check if account data is already in use. // @todo Check if username is WissKI Cloud accounts, i.e add direct by admin?. $dataToCheck['username'] = $form_state->getValue('username'); @@ -151,17 +151,19 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { try { $field = $form_state->getValues(); - $account["personname"] = $field['personname']; + $account["personName"] = $field['personName']; $account["organisation"] = $field['organisation']; $account["email"] = $field['email']; $account["username"] = $field['username']; $account["password"] = $field['password']; $account["subdomain"] = $field['subdomain']; - $this->wisskiCloudAccountManagerDaemonApiActions->addAccount($account); + $accountResponse = $this->wisskiCloudAccountManagerDaemonApiActions->addAccount($account); + dpm($accountResponse, 'accountResponse'); + $this->wisskiCloudAccountManagerDaemonApiActions->sendValidationEmail($accountResponse['user']['email'], $accountResponse['user']['validationCode']); \Drupal::messenger() - ->addMessage($this->t('The account data has been succesfully saved')); + ->addMessage($this->t('The account data has been successfully saved, please check your email for validation!')); } catch (\Exception $ex) { \Drupal::logger('wisski_cloud_account_manager')->error($ex->getMessage()); diff --git a/src/Form/WisskiCloudAccountManagerSettingsForm.php b/src/Form/WisskiCloudAccountManagerSettingsForm.php index 887efab..c32cf81 100644 --- a/src/Form/WisskiCloudAccountManagerSettingsForm.php +++ b/src/Form/WisskiCloudAccountManagerSettingsForm.php @@ -35,6 +35,27 @@ class WisskiCloudAccountManagerSettingsForm extends ConfigFormBase { /** @var \Drupal\Core\Config\ImmutableConfig $config */ $config = $this->config('wisski_cloud_account_manager.settings'); + $form['daemonUrl'] = [ + '#type' => 'url', + '#title' => $this->t('The WissKI Cloud API Daemon URL'), + '#description' => $this->t('Provide the complete base URL with protocol, domain (resp. service name in docker), ports and API path, i. e. "http://wisski_cloud_api_daemon:3000/wisski-cloud-daemon/api/v1"'), + '#default_value' => $config->get('daemonUrl'), + ]; + + $form['userPostUrlPath'] = [ + '#type' => 'url', + '#title' => $this->t('POST URL path'), + '#description' => $this->t('Provide the path to the POST endpoint, i. e. "/user"'), + '#default_value' => $config->get('userPostUrlPath'), + ]; + + $form['userFilterByData'] = [ + '#type' => 'url', + '#title' => $this->t('Filter by Data URL path'), + '#description' => $this->t('Provide the path to the Get user by data endpoint, i. e. "/user/by_data"'), + '#default_value' => $config->get('userFilterByData'), + ]; + return $form; } @@ -43,27 +64,13 @@ class WisskiCloudAccountManagerSettingsForm extends ConfigFormBase { */ public function submitForm(array &$form, FormStateInterface $form_state) { $config = $this->config('wisski_cloud_account_manager.settings'); - /* - $config->set('', $form_state->getValue('')) + + $config->set('daemonURL', $form_state->getValue('daemonURL')) + ->set('userPostUrlPath', $form_state->getValue('userPostUrlPath')) + ->set('userFilterByData', $form_state->getValue('userFilterByData')) ->save(); - */ parent::submitForm($form, $form_state); } - /** - * {@inheritdoc} - */ - public function validateForm(array &$form, FormStateInterface $form_state) { - /* - // Hash expiration validation. - $hash_expiration = intval($form_state->getValue('hash_expiration')); - if ($hash_expiration < 1) { - $form_state->setErrorByName('hash_expiration', $this->t('The miminum hash expiration time is @min_value.', ['@min_value' => $this->t('one hour')])); - } - elseif ($hash_expiration > 48) { - $form_state->setErrorByName('hash_expiration', $this->t('The maximum hash expiration time is @max_value.', ['@max_value' => $this->t('@count days', ['@count' => 2])])); - } - */ - } } diff --git a/src/WisskiCloudAccountManagerDaemonApiActions.php b/src/WisskiCloudAccountManagerDaemonApiActions.php index ff243d0..8bc0b70 100644 --- a/src/WisskiCloudAccountManagerDaemonApiActions.php +++ b/src/WisskiCloudAccountManagerDaemonApiActions.php @@ -4,7 +4,11 @@ namespace Drupal\wisski_cloud_account_manager; use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Mail\MailManagerInterface; use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Render\Markup; use Drupal\Core\StringTranslation\TranslationInterface; use GuzzleHttp\ClientInterface; @@ -13,9 +17,28 @@ use GuzzleHttp\ClientInterface; */ class WisskiCloudAccountManagerDaemonApiActions { - const DAEMON_URL = 'http://wisski_cloud_api_daemon:3000/wisski-cloud-daemon/api/v1/user/'; + use DependencySerializationTrait; - const FILTER_BY_DATA_URL_PART = 'by_data'; + /** + * The base URL of the WissKI Cloud account manager daemon. + * + * @var string + */ + private string $DAEMON_URL; + + /** + * The URL path to the POST endpoint. + * + * @var string + */ + private string $USER_POST_URL_PART = '/user'; + + /** + * The URL path to the GET endpoint. + * + * @var string + */ + private string $FILTER_BY_DATA_URL_PART = '/user/by_data'; /** * The string translation service. @@ -45,15 +68,49 @@ class WisskiCloudAccountManagerDaemonApiActions { */ protected Config $settings; + + + /** + * The mail manager. + * + * @var \Drupal\Core\Mail\MailManagerInterface + */ + protected MailManagerInterface $mailManager; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected LanguageManagerInterface $languageManager; + /** * Class constructor. */ - public function __construct(TranslationInterface $stringTranslation, MessengerInterface $messenger, ClientInterface $httpClient, ConfigFactoryInterface $configFactory) { + public function __construct( + TranslationInterface $stringTranslation, + MessengerInterface $messenger, + ClientInterface $httpClient, + ConfigFactoryInterface $configFactory, + MailManagerInterface $mailManager, + LanguageManagerInterface $languageManager) { + // Services from container. $this->stringTranslation = $stringTranslation; $this->messenger = $messenger; $this->httpClient = $httpClient; - $this->settings = $configFactory + $this->mailManager = $mailManager; + $this->languageManager = $languageManager; + + // Settings. + $settings = $configFactory ->getEditable('wisski_cloud_account_manager.settings'); + $this->settings = $settings; + + // Set the daemon URL and the URL parts class variables. + $this->DAEMON_URL = $settings->get('daemonUrl') ?: 'http://wisski_cloud_api_daemon:3000/wisski-cloud-daemon/api/v1'; + $this->USER_POST_URL_PART = $settings->get('userPostUrlPath') ?: '/user'; + $this->FILTER_BY_DATA_URL_PART = $settings->get('userFilterByData') ?: '/user/by_data'; + } /** @@ -66,16 +123,15 @@ class WisskiCloudAccountManagerDaemonApiActions { * The response from the daemon (user id with validation code). */ public function addAccount(array $account): array { - dpm($account, 'account'); $request = [ 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => json_encode($account), ]; - dpm($request, 'request'); - $response = $this->httpClient->post(self::DAEMON_URL, $request); - return json_decode($response->getBody()->getContents(), TRUE); + $userPostUrl = $this->DAEMON_URL . $this->USER_POST_URL_PART; + $response = $this->httpClient->post($userPostUrl, $request); + return array_merge(json_decode($response->getBody()->getContents(), TRUE), ['statusCode' => $response->getStatusCode()]); } /** @@ -92,7 +148,7 @@ class WisskiCloudAccountManagerDaemonApiActions { $query_string = http_build_query($dataToCheck); // Combine the base URL and the query string. - $request_url = self::DAEMON_URL . self::FILTER_BY_DATA_URL_PART . '?' . $query_string; + $request_url = $this->DAEMON_URL . $this->FILTER_BY_DATA_URL_PART . '?' . $query_string; // Send the GET request using the `drupal_http_request()` function. $response = $this->httpClient->get($request_url); @@ -114,4 +170,35 @@ class WisskiCloudAccountManagerDaemonApiActions { } } + /** + * Sends a validation email to the given email address. + * + * @param string $email + * The email address to send the validation email to. + * @param string $validationCode + * The validation code to be used in the validation link. + */ + public function sendValidationEmail(string $email, string $validationCode): void { + $module = 'wisski_cloud_account_manager'; + $key = 'wisski_cloud_account_validation'; + $langcode = $this->languageManager->getDefaultLanguage()->getId(); + $to = $email; + + $validationLink = \Drupal::request()->getSchemeAndHttpHost() . '/wisski-cloud-account-manager/validate/' . $validationCode; + + $params['message'] = Markup::create($this->stringTranslation->translate('

Please validate your account by clicking on this link or copy this to the address bar of your browser:

@validationLink

.

', ['@validationLink' => $validationLink])); + $params['subject'] = $this->stringTranslation->translate('WissKI Cloud account validation'); + + $result = $this->mailManager->mail($module, $key, $to, $langcode, $params, NULL, TRUE); + dpm($result, 'result'); + if ($result['result'] === TRUE) { + $this->messenger + ->addMessage($this->stringTranslation->translate('Email sent successfully.')); + } + else { + $this->messenger + ->addMessage($this->stringTranslation->translate('There was an error sending the email.'), 'error'); + } + } + } diff --git a/wisski_cloud_account_manager.info.yml b/wisski_cloud_account_manager.info.yml index a4d1640..4cf381f 100644 --- a/wisski_cloud_account_manager.info.yml +++ b/wisski_cloud_account_manager.info.yml @@ -1,6 +1,6 @@ name: 'WissKI cloud account manager' type: module -description: 'WissKI cloud manager with opt in service. Provides a common API for creating users for the WissKI Cloud.' +description: 'WissKI cloud manager with opt in service. Provides a create form and handles communication with WissKI Cloud API Daemon for creating users for the WissKI Cloud.' package: WissKI Cloud configure: wisski_cloud_account_manager.settings core_version_requirement: ^9 || ^10 diff --git a/wisski_cloud_account_manager.links.menu.yml b/wisski_cloud_account_manager.links.menu.yml new file mode 100644 index 0000000..8cd3622 --- /dev/null +++ b/wisski_cloud_account_manager.links.menu.yml @@ -0,0 +1,5 @@ +wisski_cloud_account.settings.menu: + title: 'WissKI cloud account settings' + description: 'WissKI cloud account settings' + parent: system.admin_config_wisski_cloud_account + route_name: wisski_cloud_account.settings diff --git a/wisski_cloud_account_manager.module b/wisski_cloud_account_manager.module new file mode 100644 index 0000000..6d1d99b --- /dev/null +++ b/wisski_cloud_account_manager.module @@ -0,0 +1,44 @@ +' . t('About') . ''; + $output .= '

' . t('This module provides a form to create a WissKI Cloud account. Create form can be found at the route "/wisski_cloud_account_manager/create" ', ['@createPage' => '/wisski_cloud_account_manager/create']). '

'; + return $output; + } +} + +/** + * Implements hook_mail(). + */ +function wisski_cloud_account_manager_mail($key, &$message, $params) { + $options = array( + 'langcode' => $message['langcode'], + ); + switch ($key) { + case 'wisski_cloud_account_validation': + $message['from'] = \Drupal::config('system.site')->get('mail'); + $message['subject'] = t('@subject', array('@subject' => $params['subject']), $options); + $message['body'][] = $params['message']; + + $headers = [ + 'Content-Type' => 'text/html; charset=UTF-8; format=flowed', + 'MIME-Version' => '1.0', + 'Content-Transfer-Encoding' => '8Bit', + 'X-Mailer' => 'Drupal', + ]; + + $message['headers'] = $headers; + + break; + } +} diff --git a/wisski_cloud_account_manager.routing.yml b/wisski_cloud_account_manager.routing.yml index fcf67e4..1643fb1 100644 --- a/wisski_cloud_account_manager.routing.yml +++ b/wisski_cloud_account_manager.routing.yml @@ -1,5 +1,14 @@ +# Site config group +system.admin_config_wisski_cloud_account: + path: '/admin/config/wisski-cloud-account-manager' + defaults: + _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage' + _title: 'WissKI cloud account manager' + requirements: + _permission: 'administer site configuration' + wisski_cloud_account.settings: - path: '/admin/config/system/wisski-cloud-account-manager/settings' + path: '/admin/config/wisski-cloud-account-manager/settings' defaults: _form: '\Drupal\wisski_cloud_account_manager\Form\WisskiCloudAccountManagerSettingsForm' _title: 'WissKI cloud account settings' @@ -18,6 +27,6 @@ wisski_cloud_account.terms_and_conditions: path: '/wisski-cloud-account-manager/terms-and-conditions' defaults: _controller: '\Drupal\wisski_cloud_account_manager\Controller\WisskiCloudAccountManagerController::termsAndConditions' - _title: 'Terms and Conditions' + _title: 'Terms and conditions' requirements: _access: 'TRUE' diff --git a/wisski_cloud_account_manager.services.yml b/wisski_cloud_account_manager.services.yml index f571156..5d97a8e 100644 --- a/wisski_cloud_account_manager.services.yml +++ b/wisski_cloud_account_manager.services.yml @@ -6,3 +6,5 @@ services: - '@messenger' - '@http_client' - '@config.factory' + - '@plugin.manager.mail' + - '@language_manager'