From bb4d5b65d56ea6b5ebfa0b185dc2df21c3412574 Mon Sep 17 00:00:00 2001 From: Robert Nasarek Date: Mon, 4 Sep 2023 00:18:13 +0200 Subject: [PATCH] more managing and validation --- .../WisskiCloudAccountManagerController.php | 33 +++- .../WisskiCloudAccountManagerCreateForm.php | 82 +++++++-- .../WisskiCloudAccountManagerSettingsForm.php | 35 ++++ ...skiCloudAccountManagerDaemonApiActions.php | 160 ++++++++++++------ ...nt-manager-account-managing-page.html.twig | 61 +++---- ...-account-manager-validation-page.html.twig | 38 +++-- wisski_cloud_account_manager.module | 13 +- wisski_cloud_account_manager.routing.yml | 38 +++-- wisski_cloud_account_manager.services.yml | 10 +- 9 files changed, 334 insertions(+), 136 deletions(-) diff --git a/src/Controller/WisskiCloudAccountManagerController.php b/src/Controller/WisskiCloudAccountManagerController.php index da9aaed..c9f54f2 100644 --- a/src/Controller/WisskiCloudAccountManagerController.php +++ b/src/Controller/WisskiCloudAccountManagerController.php @@ -47,7 +47,8 @@ class WisskiCloudAccountManagerController extends ControllerBase { */ public function termsAndConditionsPage(): array { $build = [ - '#markup' => $this->t('Hello World!'), + '#theme' => 'terms_and_conditions_page', + '#date' => date('Y'), ]; return $build; } @@ -64,17 +65,41 @@ class WisskiCloudAccountManagerController extends ControllerBase { public function validationPage(string $validationCode): array { $validationResponse = $this->wisskiCloudAccountManagerDaemonApiActions->validateAccount($validationCode); - $responseContents = json_decode($validationResponse->getBody() + $account = json_decode($validationResponse->getBody() ->getContents(), TRUE); - return [ '#theme' => 'wisski_cloud_account_manager_validation_page', - '#responseContents' => $responseContents, + '#account' => $account, '#attached' => [ 'library' => [ 'wisski_cloud_account_manager/wisski_cloud_account_manager', ], ], + '#cache' => [ + 'max-age' => 0, + ], + ]; + } + + /** + * Page list the account statuses. + * + * @return array + * The page build array. + */ + public function accountManagingPage(): array { + $accounts = $this->wisskiCloudAccountManagerDaemonApiActions->getAccounts(); + return [ + '#theme' => 'wisski_cloud_account_manager_account_managing_page', + '#accounts' => $accounts, + '#attached' => [ + 'library' => [ + 'wisski_cloud_account_manager/wisski_cloud_account_manager', + ], + ], + '#cache' => [ + 'max-age' => 0, + ], ]; } diff --git a/src/Form/WisskiCloudAccountManagerCreateForm.php b/src/Form/WisskiCloudAccountManagerCreateForm.php index 9ef2c95..7edeb11 100644 --- a/src/Form/WisskiCloudAccountManagerCreateForm.php +++ b/src/Form/WisskiCloudAccountManagerCreateForm.php @@ -3,8 +3,10 @@ namespace Drupal\wisski_cloud_account_manager\Form; use Drupal\Component\Utility\EmailValidatorInterface; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\wisski_cloud_account_manager\WisskiCloudAccountManagerDaemonApiActions; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -13,18 +15,44 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class WisskiCloudAccountManagerCreateForm extends FormBase { - /** - * @var \Drupal\wisski_cloud_account_manager\WisskiCloudAccountManagerDaemonApiActions - * The WissKi Cloud account manager daemon API actions service. - */ - protected WisskiCloudAccountManagerDaemonApiActions $wisskiCloudAccountManagerDaemonApiActions; + + /** + * The config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The email validator service. + * * @var \Drupal\Component\Utility\EmailValidatorInterface - * The email validator service. */ private EmailValidatorInterface $emailValidator; + /** + * The messenger service. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * The settings config. + * + * @var \Drupal\Core\Config\Config + */ + protected $settings; + + /** + * The WissKi Cloud account manager daemon API actions service. + * + * @var \Drupal\wisski_cloud_account_manager\WisskiCloudAccountManagerDaemonApiActions + */ + protected WisskiCloudAccountManagerDaemonApiActions $wisskiCloudAccountManagerDaemonApiActions; + /** * {@inheritdoc} */ @@ -35,14 +63,23 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { /** * Class constructor. * - * @param \Drupal\wisski_cloud_account_manager\WisskiCloudAccountManagerDaemonApiActions $wisskiCloudAccountManagerDaemonApiActions - * The WissKi Cloud account manager daemon API actions service. + * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory + * The config factory service. * @param \Drupal\Component\Utility\EmailValidatorInterface $emailValidator * The email validator service. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger service. + * @param \Drupal\wisski_cloud_account_manager\WisskiCloudAccountManagerDaemonApiActions $wisskiCloudAccountManagerDaemonApiActions + * The WissKi Cloud account manager daemon API actions service. */ - public function __construct(WisskiCloudAccountManagerDaemonApiActions $wisskiCloudAccountManagerDaemonApiActions, EmailValidatorInterface $emailValidator) { + public function __construct(ConfigFactoryInterface $configFactory, EmailValidatorInterface $emailValidator, MessengerInterface $messenger, WisskiCloudAccountManagerDaemonApiActions $wisskiCloudAccountManagerDaemonApiActions) { + $this->configFactory = $configFactory; + $settings = $configFactory + ->getEditable('wisski_cloud_account_manager.settings'); + $this->settings = $settings; $this->wisskiCloudAccountManagerDaemonApiActions = $wisskiCloudAccountManagerDaemonApiActions; $this->emailValidator = $emailValidator; + $this->messenger = $messenger; } /** @@ -53,8 +90,10 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { */ public static function create(ContainerInterface $container) { return new static( - $container->get('wisski_cloud_account_manager.daemon_api.actions'), + $container->get('config.factory'), $container->get('email.validator'), + $container->get('messenger'), + $container->get('wisski_cloud_account_manager.daemon_api.actions'), ); } @@ -102,8 +141,8 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { $form['subdomain'] = [ '#type' => 'textfield', '#title' => $this->t('Subdomain'), - '#maxlength' => 12, - '#description' => $this->t('WissKI cloud subdomain. Only small caps (a-z), underscore (_), minus (-) and 12 letter maximum allowed, i.e. "my_wisski" will end in "my_wisski.wisski.cloud".'), + '#maxlength' => 64, + '#description' => $this->t('WissKI cloud subdomain. Only small caps (a-z), underscore (_), minus (-) and 64 letter maximum allowed, i.e. "my_wisski" will end in "my_wisski.wisski.cloud".'), '#pattern' => '[a-z]+([_-]{1}[a-z]+)*', '#required' => TRUE, ]; @@ -137,6 +176,15 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { $response = $this->wisskiCloudAccountManagerDaemonApiActions->checkAccountData($dataToCheck); + if (!$response['success']) { + $this->messenger->addError('Can not communicate with the provision daemon, please try again later or write an email to cloud@wiss-ki.eu.'); + } + if (strlen($dataToCheck['username']) < 3) { + $form_state->setErrorByName('username', $this->t('The username "@username" is too short, please use at least 3 characters.', ['@username' => $dataToCheck['username']])); + } + if (in_array($dataToCheck['username'], explode(',', $this->settings->get('usernameBlacklist')))) { + $form_state->setErrorByName('username', $this->t('The username "@username" is not allowed.', ['@username' => $dataToCheck['username']])); + } if ($response['accountData']['accountWithUsername']) { $form_state->setErrorByName('username', $this->t('The username "@username" is already in use.', ['@username' => $dataToCheck['username']])); } @@ -145,6 +193,13 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { $form_state->setErrorByName('email', $this->t('The email "@email" is already in use.', ['@email' => $dataToCheck['email']])); } + if (strlen($dataToCheck['subdomain']) < 3) { + $form_state->setErrorByName('subdomain', $this->t('The subdomain "@subdomain" is too short, please use at least 3 characters.', ['@subdomain' => $dataToCheck['subdomain']])); + } + + if (in_array($dataToCheck['subdomain'], explode(',', $this->settings->get('subdomainBlacklist')))) { + $form_state->setErrorByName('subdomain', $this->t('The subdomain "@subdomain" is not allowed.', ['@subdomain' => $dataToCheck['subdomain']])); + } if ($response['accountData']['accountWithSubdomain']) { $form_state->setErrorByName('subdomain', $this->t('The subdomain "@subdomain" is already in use.', ['@subdomain' => $dataToCheck['subdomain']])); } @@ -170,8 +225,7 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { $account["subdomain"] = $field['subdomain']; $accountResponse = $this->wisskiCloudAccountManagerDaemonApiActions->addAccount($account); - dpm($accountResponse, 'accountResponse'); - $this->wisskiCloudAccountManagerDaemonApiActions->sendValidationEmail($accountResponse['account']['email'], $accountResponse['account']['validationCode']); + $this->wisskiCloudAccountManagerDaemonApiActions->sendValidationEmail($accountResponse['data']['email'], $accountResponse['data']['validationCode']); $this->messenger() ->addMessage($this->t('The account data has been successfully saved, please check your email for validation!')); diff --git a/src/Form/WisskiCloudAccountManagerSettingsForm.php b/src/Form/WisskiCloudAccountManagerSettingsForm.php index 6ece673..ed63ad9 100644 --- a/src/Form/WisskiCloudAccountManagerSettingsForm.php +++ b/src/Form/WisskiCloudAccountManagerSettingsForm.php @@ -42,6 +42,13 @@ class WisskiCloudAccountManagerSettingsForm extends ConfigFormBase { '#default_value' => $config->get('daemonUrl'), ]; + $form['allAccounts'] = [ + '#type' => 'url', + '#title' => $this->t('All accounts URL path'), + '#description' => $this->t('Provide the endpoint to the GET endpoint for all accounts, i. e. "http://wisski_cloud_api_daemon:3000/wisski-cloud-daemon/api/v1/account/all"'), + '#default_value' => $config->get('allAccounts'), + ]; + $form['accountPostUrlPath'] = [ '#type' => 'url', '#title' => $this->t('POST URL path'), @@ -63,9 +70,35 @@ class WisskiCloudAccountManagerSettingsForm extends ConfigFormBase { '#default_value' => $config->get('accountValidation'), ]; + $form['usernameBlacklist'] = [ + '#type' => 'textfield', + '#title' => $this->t('Username blacklist'), + '#description' => $this->t('Provide blocked usernames with a comma separated list, i. e. "admin,root"'), + '#default_value' => $config->get('usernameBlacklist'), + ]; + $form['subdomainBlacklist'] = [ + '#type' => 'textfield', + '#title' => $this->t('Subdomain blacklist'), + '#description' => $this->t('Provide blocked subdomain with a comma separated list, i. e. "www,admin,root"'), + '#default_value' => $config->get('subdomainBlacklist'), + ]; + return $form; } + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state): void { + parent::validateForm($form, $form_state); + if (!preg_match("/^(?:\w+(?:,\w+)*)?$/", $form_state->getValue('usernameBlacklist'))) { + $form_state->setErrorByName('usernameBlacklist', $this->t('The username blacklist is not valid. Only words separated by commas are allowed.')); + } + if (!preg_match("/^(?:\w+(?:,\w+)*)?$/", $form_state->getValue('subdomainBlacklist'))) { + $form_state->setErrorByName('subdomainBlacklist', $this->t('The subdomain blacklist is not valid. Only words separated by commas are allowed.')); + } + } + /** * {@inheritdoc} */ @@ -77,6 +110,8 @@ class WisskiCloudAccountManagerSettingsForm extends ConfigFormBase { ->set('accountFilterByData', $form_state->getValue('accountFilterByData')) ->set('accountProvisionAndValidationCheck', $form_state->getValue('accountProvisionAndValidationCheck')) ->set('accountValidation', $form_state->getValue('accountValidation')) + ->set('usernameBlacklist', $form_state->getValue('usernameBlacklist')) + ->set('subdomainBlacklist', $form_state->getValue('subdomainBlacklist')) ->save(); parent::submitForm($form, $form_state); diff --git a/src/WisskiCloudAccountManagerDaemonApiActions.php b/src/WisskiCloudAccountManagerDaemonApiActions.php index 1ac70cd..93630d1 100644 --- a/src/WisskiCloudAccountManagerDaemonApiActions.php +++ b/src/WisskiCloudAccountManagerDaemonApiActions.php @@ -6,6 +6,7 @@ use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Mail\MailManagerInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Render\Markup; @@ -21,11 +22,11 @@ class WisskiCloudAccountManagerDaemonApiActions { use DependencySerializationTrait; /** - * The base URL of the WissKI Cloud account manager daemon. + * The URL path to all account data GET endpoint. * * @var string */ - private string $DAEMON_URL; + private string $ALL_ACCOUNTS = '/account/all'; /** * The URL path to the POST endpoint. @@ -35,11 +36,11 @@ class WisskiCloudAccountManagerDaemonApiActions { private string $ACCOUNT_POST_URL_PART = '/account'; /** - * The URL path to the GET endpoint. + * The URL path to provision and validation GET endpoint. * * @var string */ - private string $FILTER_BY_DATA_URL_PART = '/account/by_data'; + private string $ACCOUNT_PROVISION_AND_VALIDATION_URL_PART; /** * The URL path to provision and validation GET endpoint. @@ -49,25 +50,18 @@ class WisskiCloudAccountManagerDaemonApiActions { private string $ACCOUNT_VALIDATION_URL_PART = '/account/validation'; /** - * The string translation service. + * The base URL of the WissKI Cloud account manager daemon. * - * @var \Drupal\Core\StringTranslation\TranslationInterface + * @var string */ - protected TranslationInterface $stringTranslation; + private string $DAEMON_URL; /** - * The messenger service. + * The URL path to the filter by account data GET endpoint. * - * @var \Drupal\Core\Messenger\MessengerInterface + * @var string */ - protected MessengerInterface $messenger; - - /** - * The HTTP client. - * - * @var \GuzzleHttp\ClientInterface - */ - protected ClientInterface $httpClient; + private string $FILTER_BY_DATA_URL_PART = '/account/by_data'; /** * The settings config. @@ -77,11 +71,25 @@ class WisskiCloudAccountManagerDaemonApiActions { protected Config $settings; /** - * The mail manager. + * The HTTP client. * - * @var \Drupal\Core\Mail\MailManagerInterface + * @var \GuzzleHttp\ClientInterface */ - protected MailManagerInterface $mailManager; + protected ClientInterface $httpClient; + + /** + * The logger factory. + * + * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface + */ + protected LoggerChannelFactoryInterface $loggerFactory; + + /** + * The messenger service. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected MessengerInterface $messenger; /** * The language manager. @@ -90,19 +98,35 @@ class WisskiCloudAccountManagerDaemonApiActions { */ protected LanguageManagerInterface $languageManager; + /** + * The mail manager. + * + * @var \Drupal\Core\Mail\MailManagerInterface + */ + protected MailManagerInterface $mailManager; + + /** + * The string translation service. + * + * @var \Drupal\Core\StringTranslation\TranslationInterface + */ + protected TranslationInterface $stringTranslation; + /** * Class constructor. */ public function __construct( - TranslationInterface $stringTranslation, - MessengerInterface $messenger, - ClientInterface $httpClient, ConfigFactoryInterface $configFactory, + ClientInterface $httpClient, + LanguageManagerInterface $languageManager, + LoggerChannelFactoryInterface $loggerFactory, + MessengerInterface $messenger, MailManagerInterface $mailManager, - LanguageManagerInterface $languageManager + TranslationInterface $stringTranslation, ) { // Services from container. $this->stringTranslation = $stringTranslation; + $this->loggerFactory = $loggerFactory; $this->messenger = $messenger; $this->httpClient = $httpClient; $this->mailManager = $mailManager; @@ -115,9 +139,10 @@ class WisskiCloudAccountManagerDaemonApiActions { // 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('accountPostUrlPath') ?: '/account'; + $this->ALL_ACCOUNTS = $settings->get('allAccounts') ?: '/account/all'; + $this->ACCOUNT_POST_URL_PART = $settings->get('accountPostUrlPath') ?: '/account'; $this->FILTER_BY_DATA_URL_PART = $settings->get('accountFilterByData') ?: '/account/by_data'; - $this->USER_PROVISION_AND_VALIDATION_URL_PART = $settings->get('accountProvisionAndValidationUrlPart') ?: '/account/provision_and_validation'; + $this->ACCOUNT_PROVISION_AND_VALIDATION_URL_PART = $settings->get('accountProvisionAndValidationUrlPart') ?: '/account/provision_and_validation'; $this->ACCOUNT_VALIDATION_URL_PART = $settings->get('accountValidationUrlPart') ?: '/account/validation'; } @@ -156,30 +181,68 @@ class WisskiCloudAccountManagerDaemonApiActions { * The response from the daemon. */ public function checkAccountData(array $dataToCheck): array { - // Build the query string from the parameters. - $query_string = http_build_query($dataToCheck); + try { + // Build the query string from the parameters. + $query_string = http_build_query($dataToCheck); + // Combine the base URL and the 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); + + // Check the response and handle the data accordingly. + if ($response->getStatusCode() == 200) { + // Request successful, handle the data in $response->data. + return [ + "message" => "Get account data", + "accountData" => json_decode($response->getBody()->getContents(), + TRUE)['data'], + 'success' => TRUE, + ]; + } + else { + // Request failed, handle the error. + return [ + "message" => 'Request failed with code: ' . $response->getStatusCode(), + "accountData" => [ + 'accountWithUsername' => NULL, + 'accountWithEmail' => NULL, + 'accountWithSubdomain' => NULL, + ], + 'success' => FALSE, + ]; + } + } + catch (\Exception $e) { + // Request failed, handle the error. + $this->loggerFactory + ->get('wisski_cloud_account_manager') + ->error('Request failed with exception: ' . $e->getMessage()); + return [ + "message" => 'Request failed with exception: ' . $e->getMessage(), + "accountData" => [ + 'accountWithUsername' => NULL, + 'accountWithEmail' => NULL, + 'accountWithSubdomain' => NULL, + ], + 'success' => FALSE, + ]; + } + } + + /** + * Gets all accounts from the WissKI Cloud account manager daemon. + * + * @return array + * The accounts response from the daemon. + */ + public function getAccounts(): array { // Combine the base URL and the query string. - $request_url = $this->DAEMON_URL . $this->FILTER_BY_DATA_URL_PART . '?' . $query_string; - + $request_url = $this->DAEMON_URL . $this->ALL_ACCOUNTS; // Send the GET request using the `drupal_http_request()` function. $response = $this->httpClient->get($request_url); - - // Check the response and handle the data accordingly. - if ($response->getStatusCode() == 200) { - // Request successful, handle the data in $response->data. - return [ - "message" => "Get account data", - "accountData" => json_decode($response->getBody()->getContents(), TRUE), - ]; - } - else { - // Request failed, handle the error. - return [ - "message" => 'Request failed with code: ' . $response->getStatusCode(), - "accountData" => [], - ]; - } + return json_decode($response->getBody()->getContents(), TRUE); } /** @@ -194,10 +257,8 @@ class WisskiCloudAccountManagerDaemonApiActions { public function validateAccount(string $validationCode): ResponseInterface { $url = $this->DAEMON_URL . $this->ACCOUNT_VALIDATION_URL_PART . '/' . $validationCode; return $this->httpClient->put($url); - } - /** * Sends a validation email to the given email address. * @@ -213,13 +274,12 @@ class WisskiCloudAccountManagerDaemonApiActions { $to = $email; $validationLink = \Drupal::request() - ->getSchemeAndHttpHost() . '/wisski-cloud-account-manager/validate/' . $validationCode; + ->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.')); diff --git a/templates/wisski-cloud-account-manager-account-managing-page.html.twig b/templates/wisski-cloud-account-manager-account-managing-page.html.twig index fdda2cb..e457fe3 100644 --- a/templates/wisski-cloud-account-manager-account-managing-page.html.twig +++ b/templates/wisski-cloud-account-manager-account-managing-page.html.twig @@ -1,56 +1,57 @@ -{# wisski_cloud_account_manager/templates/wisski_cloud_account_manager_validation_page.html.twig #} +{# wisski_cloud_account_manager/templates/wisski_cloud_account_manager_account_managing_page.html.twig #}
- {% if responseContents is not empty %} + {% if accounts is not empty %} + - + + + {% for item in accounts.data %} - - - - + + + + + - - + + + {% endfor %} -
- - {% endif %}
diff --git a/templates/wisski-cloud-account-manager-validation-page.html.twig b/templates/wisski-cloud-account-manager-validation-page.html.twig index 474fb4e..68acfd4 100644 --- a/templates/wisski-cloud-account-manager-validation-page.html.twig +++ b/templates/wisski-cloud-account-manager-validation-page.html.twig @@ -1,6 +1,6 @@ {# wisski_cloud_account_manager/templates/wisski_cloud_account_manager_validation_page.html.twig #}
- {% if responseContents is not empty %} + {% if account is not empty %} @@ -9,41 +9,43 @@ + - - - - + + + + - +