diff --git a/css/style.css b/css/style.css index 47aa2fb..10b2abe 100755 --- a/css/style.css +++ b/css/style.css @@ -1,43 +1,4 @@ -/* Reset the table styles */ -table.wcam--table { - border-collapse: collapse; - width: 100%; -} - -/* Style for table headers */ -table.wcam--table th { - background-color: #f2f2f2; - padding: 8px; - text-align: center; -} - -/* Alternate row background color */ -table.wcam--table tr:nth-child(even) { - background-color: #f2f2f2; -} - -/* Style for table cells */ -table.wcam--table td { - padding: 8px; - border: 1px solid #dddddd; -} - -/* Center-align certain cells */ -table.wcam--table td.valid, -table.wcam--table td.provisioned { - text-align: center; -} - -.wisski-cloud-account-manager-success { - background-color: green; -} -.wisski-cloud-account-manager-warning { - background-color: orange; -} -.wisski-cloud-account-manager-error { - background-color: red; -} - -.wisski-cloud-account-manager-center { - text-align: center; +.spinner-border { + height: 1em !important; + width: 1em !important; } diff --git a/js/accountOptions.js b/js/accountOptions.js old mode 100644 new mode 100755 index 6416bbd..3ad3dd9 --- a/js/accountOptions.js +++ b/js/accountOptions.js @@ -5,30 +5,37 @@ */ Drupal.behaviors.accountOptions = { attach: function (context, settings) { - once('accountOptions', '#wcam--table', context).forEach(function (form) { + once('accountOptions', '#wcam--table', context).forEach(function () { $('.wcam--select').change(function () { - console.log($(this)) let selectedOption = $(this).val(); - let itemId = $(this).closest('tr').find('.wcam--row--item-id').text(); + let aid = $(this).closest('tr').find('.wcam--row--account-id').text().trim(); switch (selectedOption) { case 'delete': - // Führen Sie hier Ihren JavaScript-Code für 'delete' aus. - - console.log('delete:', itemId); + // Construct the URL for the delete route. + let deleteUrl = Drupal.url('wisski-cloud-account-manager/delete/' + aid); + // Redirect to the delete route. + window.location.href = deleteUrl; break; case 'edit': - // Führen Sie hier Ihren JavaScript-Code für 'edit' aus. - console.log('Edit:', itemId); + console.log('Edit:', aid); break; case 'provise': - // Führen Sie hier Ihren JavaScript-Code für 'provise' aus. - console.log('Provise', itemId); + // Construct the URL for the provise route. + let proviseUrl = Drupal.url('wisski-cloud-account-manager/provise/' + aid); + // Redirect to the provise route. + window.location.href = proviseUrl; + break; + + case 'purge': + // Construct the URL for the purge route. + let purgeUrl = Drupal.url('wisski-cloud-account-manager/purge/' + aid); + // Redirect to the purge route. + window.location.href = purgeUrl; break; case 'validate': - // Führen Sie hier Ihren JavaScript-Code für 'validate' aus. console.log('Die Option "validate" wurde ausgewählt.'); break; diff --git a/js/provisionStatus.js b/js/provisionStatus.js new file mode 100644 index 0000000..015689a --- /dev/null +++ b/js/provisionStatus.js @@ -0,0 +1,37 @@ +(function ($, Drupal, once) { + Drupal.behaviors.wcamProvisionStatus = { + attach: function (context, settings) { + once('wcamProvisionStatus', 'html', context).forEach(function () { + // Get the process idle animation. + let $animation = $('#process-idle-animation'); + $animation.show(); // Show the spinner. + // Get the aid and initial provision status from the HTML. + let $rows = $('.wcam--table--row'); + $rows.each(function () { + let $row = $(this); + let aid = $row.find('.wcam--row--account-id').text().trim(); + let initialStatus = $row.find('#provision-status--aid-' + aid).text().trim(); + // If the initial provision status is 'ongoing', start checking the provision status. + if (initialStatus === 'ongoing' || initialStatus === 'unknown') { + let intervalId = setInterval(function () { + $.get('/wisski-cloud-account-manager/provision-status/' + aid, function (data) { + // Update the provision status on the page. + let $status = $('#provision-status--aid-' + aid); + $status.text(data.status); + // If the provision status is not 'ongoing', stop the process idle animation. + if (data.status !== 'ongoing') { + if (data.status !== 'unknown') { + $row.find('#provision-status--row--aid-' + aid).text(data.status); + $animation.hide(); // Hide the spinner. + clearInterval(intervalId); + location.reload(); + } + } + }); + }, 3000); + } + }); + }); + } + }; +})(jQuery, Drupal, once); diff --git a/src/Access/AccountRouteAccessCheck.php b/src/Access/AccountRouteAccessCheck.php new file mode 100644 index 0000000..c88803c --- /dev/null +++ b/src/Access/AccountRouteAccessCheck.php @@ -0,0 +1,36 @@ +currentUser = $currentUser; + $this->database = $database; + } + + public function access($aid) { + // If the user has the "Administer WissKI Cloud Account Manager" role, allow access. + if ($this->currentUser->hasPermission('Administer WissKI Cloud Account Manager')) { + return AccessResult::allowed()->cachePerUser(); + } + // Otherwise, check if the aid matches the user's aid in the database. + $uid = $this->currentUser->id(); + $query = $this->database->select('wisski_cloud_account_manager_accounts', 'w') + ->fields('w', ['aid']) + ->condition('uid', $uid) + ->condition('aid', $aid) + ->execute(); + + $result = $query->fetchField(); + + return AccessResult::allowedIf($result)->cachePerUser(); + } +} diff --git a/src/Controller/WisskiCloudAccountManagerController.php b/src/Controller/WisskiCloudAccountManagerController.php index 5a651d7..722f43b 100755 --- a/src/Controller/WisskiCloudAccountManagerController.php +++ b/src/Controller/WisskiCloudAccountManagerController.php @@ -5,6 +5,7 @@ namespace Drupal\wisski_cloud_account_manager\Controller; use Drupal\Core\Controller\ControllerBase; use Drupal\wisski_cloud_account_manager\WisskiCloudAccountManagerDaemonApiActions; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\JsonResponse; /** * The Wisski Cloud account manager info controller. @@ -39,6 +40,63 @@ class WisskiCloudAccountManagerController extends ControllerBase { ); } + /** + * Page list the account statuses. + * + * @return array + * The page build array. + */ + public function accountManagingPage(): array { + $currentUser = \Drupal::currentUser(); + $healthCheck = $this->wisskiCloudAccountManagerDaemonApiActions->healthCheck(); + $accounts = $this->wisskiCloudAccountManagerDaemonApiActions->getAccounts(); + // If the user is not an admin, filter the accounts to only include their own. + if (!$currentUser->hasPermission('admister wisski cloud account manager')) { + $accounts = array_filter($accounts, function($account) use ($currentUser) { + return $account['uid'] === $currentUser->id(); + }); + } + return [ + '#theme' => 'wisski_cloud_account_manager_account_managing_page', + '#accounts' => $accounts, + '#healthCheck' => $healthCheck, + '#attached' => [ + 'library' => [ + 'wisski_cloud_account_manager/accountOptions', + 'wisski_cloud_account_manager/globalStyling', + 'wisski_cloud_account_manager/provisionStatus' + ], + ], + '#cache' => [ + 'max-age' => 0, + ], + ]; + } + + // @todo Implement healthcheckPage(). + public function healthcheckPage(): array { + return [ + '#theme' => 'wisski_cloud_account_manager_healthcheck_page', + '#healthCheck' => $this->wisskiCloudAccountManagerDaemonApiActions->healthCheck(), + ]; + } + + public function provisionStatusPage($aid) { + $accounts = $this->wisskiCloudAccountManagerDaemonApiActions->getAccounts($aid); + // Return the status as a JSON response. + switch ($accounts[0]['provisioned']) { + case '0': + return new JsonResponse(['status' => 'no']); + case '1': + return new JsonResponse(['status' => 'ongoing']); + case '2': + return new JsonResponse(['status' => 'yes']); + case '3': + return new JsonResponse(['status' => 'unknown']); + } + return new JsonResponse(['status' => $accounts[0]['provisioned']]); + } + /** * Info page for terms and conditions. * @@ -63,41 +121,13 @@ class WisskiCloudAccountManagerController extends ControllerBase { * The page build array. */ public function validationPage(string $validationCode): array { - $account = $this->wisskiCloudAccountManagerDaemonApiActions->validateAccount($validationCode); + $user = $this->wisskiCloudAccountManagerDaemonApiActions->validateAccount($validationCode); + return [ - '#theme' => 'wisski_cloud_account_manager_validation_page', - '#account' => $account, - '#attached' => [ - 'library' => [ - 'wisski_cloud_account_manager/wisski_cloud_account_manager', - ], - ], - '#cache' => [ - 'max-age' => 0, - ], + '#markup' => $this->t('Your WissKI Cloud account for user :name is now validated. You can now login and start a provision!.', [ + ':name' => $user['name'], + ]), ]; } - - /** - * 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 a88890a..cc63d16 100755 --- a/src/Form/WisskiCloudAccountManagerCreateForm.php +++ b/src/Form/WisskiCloudAccountManagerCreateForm.php @@ -16,9 +16,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class WisskiCloudAccountManagerCreateForm extends FormBase { - - - /** * The config factory service. * @@ -126,7 +123,7 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { '#type' => 'textfield', '#title' => $this->t('Username'), '#maxlength' => 20, - '#description' => $this->t('WissKI cloud login user. Only small caps (a-z), underscore (_), minus (-) and 20 letter maximum allowed, i.e. "wisski_user".'), + '#description' => $this->t('WissKI cloud login - NOT your password for your instance - this will be send separately. Only small caps (a-z), underscore (_), minus (-) and 20 letter maximum allowed, i.e. "wisski_user".'), '#pattern' => '[a-z]+([_-]{1}[a-z]+)*', '#required' => TRUE, ]; @@ -175,38 +172,54 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { $dataToCheck['email'] = $form_state->getValue('email'); $dataToCheck['emailProvider'] = explode('@', $dataToCheck['email'])[1]; $dataToCheck['subdomain'] = $form_state->getValue('subdomain'); + $dataToCheck['usernameBlacklist'] = $this->settings->get('usernameBlacklist') ?? ''; + $dataToCheck['emailProviderBlacklist'] = $this->settings->get('emailProviderBlacklist') ?? ''; + $dataToCheck['subdomainBlacklist'] = $this->settings->get('subdomainBlacklist') ?? ''; - $response = $this->wisskiCloudAccountManagerDaemonApiActions->checkAccountData($dataToCheck); + // Check if username is too short. + if (strlen($dataToCheck['username']) < 2) { + $form_state->setErrorByName('username', $this->t('The username "@username" is too short, please use at least 2 characters.', ['@username' => $dataToCheck['username']])); + } - 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'], preg_split('/\r\n|\r|\n/', $this->settings->get('usernameBlacklist')))) { + // Check if username is in blacklist. + if (in_array($dataToCheck['username'], preg_split('/\r\n|\r|\n/', $dataToCheck['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']])); + + // Check if username only contains lowercase letters, numbers, and underscores + if (!preg_match('/^[a-z0-9_]+$/', $dataToCheck['username'])) { + $form_state->setErrorByName('username', t('Username can only contain lowercase letters, numbers, and underscores.')); } - if (in_array($dataToCheck['emailProvider'], preg_split('/\r\n|\r|\n/', $this->settings->get('emailProviderBlacklist')))) { + // Check if username is unique + $userFromUsername = \Drupal::entityTypeManager()->getStorage('user')->loadByProperties(['name' =>$dataToCheck['username']]); + if (!empty($userFromUsername)) { + $form_state->setErrorByName('username', $this->t('The username is already taken.')); + } + + // Check if email provider is in blacklist. + if (in_array($dataToCheck['emailProvider'], preg_split('/\r\n|\r|\n/', $dataToCheck['emailProviderBlacklist']))) { $form_state->setErrorByName('email', $this->t('The email provider "@provider"is not allowed.', ['@provider' => $dataToCheck['emailProvider']])); } - if ($response['accountData']['accountWithEmail']) { + // Check if email is already in use. + $userFromEmail = \Drupal::entityTypeManager()->getStorage('user')->loadByProperties(['name' => $dataToCheck['email']]); + if (!empty($userFromEmail)) { $form_state->setErrorByName('email', $this->t('The email "@email" is already in use.', ['@email' => $dataToCheck['email']])); } + // Check if subdomain is too short. 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'], preg_split('/\r\n|\r|\n/', $this->settings->get('subdomainBlacklist')))) { + // Check if subdomain is in blacklist. + if (in_array($dataToCheck['subdomain'], preg_split('/\r\n|\r|\n/', $dataToCheck['emailProviderBlacklist']))) { $form_state->setErrorByName('subdomain', $this->t('The subdomain "@subdomain" is not allowed.', ['@subdomain' => $dataToCheck['subdomain']])); } - if ($response['accountData']['accountWithSubdomain']) { + + // Check if subdomain is already in use. + if ($this->wisskiCloudAccountManagerDaemonApiActions->checkForRedundantAccountData('subdomain', $dataToCheck['subdomain'])) { $form_state->setErrorByName('subdomain', $this->t('The subdomain "@subdomain" is already in use.', ['@subdomain' => $dataToCheck['subdomain']])); } @@ -230,11 +243,8 @@ class WisskiCloudAccountManagerCreateForm extends FormBase { $account["password"] = $field['password']; $account["subdomain"] = $field['subdomain']; - $accountResponse = $this->wisskiCloudAccountManagerDaemonApiActions->addAccount($account); - if (!$accountResponse['success']) { - throw new Exception('Get no valid response from api daemon. Can not send validiation email.'); - }; - $this->wisskiCloudAccountManagerDaemonApiActions->sendValidationEmail($accountResponse['data']['email'], $accountResponse['data']['validationCode']); + $user = $this->wisskiCloudAccountManagerDaemonApiActions->addAccount($account); + $this->wisskiCloudAccountManagerDaemonApiActions->sendValidationEmail($user['email'], $user['personName'], $user['validationCode']); $this->messenger() ->addMessage($this->t('The account data has been successfully saved, please check your email for validation!')); diff --git a/src/Form/WisskiCloudAccountManagerDeleteForm.php b/src/Form/WisskiCloudAccountManagerDeleteForm.php new file mode 100755 index 0000000..1ac53de --- /dev/null +++ b/src/Form/WisskiCloudAccountManagerDeleteForm.php @@ -0,0 +1,111 @@ +wisskiCloudAccountManagerDaemonApiActions = $wisskiCloudAccountManagerDaemonApiActions; + $aid = \Drupal::routeMatch()->getParameter('aid'); + $this->account = $this->wisskiCloudAccountManagerDaemonApiActions->getAccounts($aid)[0]; + } + + /** + * Populate the reachable variables from services. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The class container. + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('wisski_cloud_account_manager.daemon_api.actions'), + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Do you really want to delete your WissKI Cloud Instance?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return Url::fromRoute('wisski_cloud_account_manager.validate') + ->setRouteParameter('validationCode', $this->account['validation_code']); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->t('This will delete your WissKI Cloud instance. This action cannot be undone. Your Drupal user and WissKI Cloud account will remain.'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + + $form['account_table'] = [ + '#type' => 'table', + '#rows' => [ + [$this->t('Subdomain'), $this->account['subdomain']], + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + $aid = \Drupal::routeMatch()->getParameter('aid'); + $this->wisskiCloudAccountManagerDaemonApiActions->crudInstance('delete', $aid); + $form_state->setRedirect('wisski_cloud_account_manager.manage'); + } + +} diff --git a/src/Form/WisskiCloudAccountManagerProvisionForm.php b/src/Form/WisskiCloudAccountManagerProvisionForm.php new file mode 100755 index 0000000..631ae32 --- /dev/null +++ b/src/Form/WisskiCloudAccountManagerProvisionForm.php @@ -0,0 +1,115 @@ +wisskiCloudAccountManagerDaemonApiActions = $wisskiCloudAccountManagerDaemonApiActions; + } + + /** + * Populate the reachable variables from services. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The class container. + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('wisski_cloud_account_manager.daemon_api.actions'), + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Do you really want to start WissKI Cloud provision?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return Url::fromRoute('wisski_cloud_account_manager.settings'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->t('This will start a WissKI Cloud account provision.'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Provise a WissKI Cloud account'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $aid = \Drupal::routeMatch()->getParameter('aid'); + $account = $this->wisskiCloudAccountManagerDaemonApiActions->getAccounts($aid)[0]; + $form['info'] = [ + '#type' => 'table', + '#header' => [ + $this->t('Account id'), + $this->t('Account name'), + $this->t('Account email'), + $this->t('Domain'), + + ], + '#rows' => [ + [ + $account['aid'], + $account['name'], + $account['mail'], + $account['subdomain'] . '.wisski.cloud', + ], + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + $aid = \Drupal::routeMatch()->getParameter('aid'); + + $result = $this->wisskiCloudAccountManagerDaemonApiActions->crudInstance('create', $aid); + $form_state->setRedirect('wisski_cloud_account_manager.manage'); + } + +} diff --git a/src/Form/WisskiCloudAccountManagerPurgeForm.php b/src/Form/WisskiCloudAccountManagerPurgeForm.php new file mode 100755 index 0000000..553e459 --- /dev/null +++ b/src/Form/WisskiCloudAccountManagerPurgeForm.php @@ -0,0 +1,105 @@ +wisskiCloudAccountManagerDaemonApiActions = $wisskiCloudAccountManagerDaemonApiActions; + } + + /** + * Populate the reachable variables from services. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The class container. + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('wisski_cloud_account_manager.daemon_api.actions'), + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Do you really want to purge your WissKI Cloud account?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return Url::fromRoute('wisski_cloud_account_manager.settings'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->t('This will delete your WissKI Cloud account, your Drupal user, your WissKI Cloud instance and all data associated with it. This action cannot be undone.'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Purge'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $aid = \Drupal::routeMatch()->getParameter('aid'); + $account = $this->wisskiCloudAccountManagerDaemonApiActions->getAccounts($aid)[0]; + $form['account_table'] = [ + '#type' => 'table', + '#rows' => [ + [$this->t('Subdomain'), $account['subdomain']], + [$this->t('Account name'), $account['name'] ?: $this->t('Seems that Drupal user has already been deleted, delete the remaining account data and instance.')], + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + $aid = \Drupal::routeMatch()->getParameter('aid'); + $this->wisskiCloudAccountManagerDaemonApiActions->purgeAccount($aid); + + $form_state->setRedirect('wisski_cloud_account_manager.manage'); + } + +} diff --git a/src/Form/WisskiCloudAccountManagerSettingsForm.php b/src/Form/WisskiCloudAccountManagerSettingsForm.php index 6d380a5..f631eff 100755 --- a/src/Form/WisskiCloudAccountManagerSettingsForm.php +++ b/src/Form/WisskiCloudAccountManagerSettingsForm.php @@ -42,32 +42,39 @@ class WisskiCloudAccountManagerSettingsForm extends ConfigFormBase { '#default_value' => $config->get('daemonUrl'), ]; - $form['allAccounts'] = [ + $form['wsUrl'] = [ '#type' => 'textfield', - '#title' => $this->t('All accounts URL path'), - '#description' => $this->t('Provide the endpoint to the GET endpoint for all accounts, i. e. "/account/all"'), - '#default_value' => $config->get('allAccounts'), + '#title' => $this->t('The websocket URL of the WissKI Cloud'), + '#description' => $this->t('Provide the complete base URL with protocol, domain (resp. service name in docker), ports and API path, i. e. "wss://panel.wisski.cloud/api/v1/ws"'), + '#default_value' => $config->get('wsUrl'), ]; - $form['accountPostUrlPath'] = [ - '#type' => 'textfield', - '#title' => $this->t('POST URL path'), - '#description' => $this->t('Provide the path to the POST endpoint, i. e. "/account"'), - '#default_value' => $config->get('accountPostUrlPath'), + $form['wsToken'] = [ + '#type' => 'password', + '#title' => $this->t('The websocket access token of the WissKI Cloud provisioning account.'), + '#description' => $this->t('Provide the access token for the websocket, i. e. "1234567890"'), + '#default_value' => $config->get('wsToken'), ]; - $form['accountFilterByData'] = [ + $form['provisionRoute'] = [ '#type' => 'textfield', - '#title' => $this->t('Filter by Data URL path'), - '#description' => $this->t('Provide the path to the Get account by data endpoint, i. e. "/account/by_data"'), - '#default_value' => $config->get('accountFilterByData'), + '#title' => $this->t('Instance provision URL path'), + '#description' => $this->t('Provide the path to the account validation PUT endpoint, i. e. "/provision"'), + '#default_value' => $config->get('provisionRoute'), ]; - $form['accountValidation'] = [ + $form['deleteRoute'] = [ '#type' => 'textfield', - '#title' => $this->t('User Validation URL path'), - '#description' => $this->t('Provide the path to the account validation PUT endpoint, i. e. "/account/validation"'), - '#default_value' => $config->get('accountValidation'), + '#title' => $this->t('Instance delete URL path'), + '#description' => $this->t('Provide the path to the account validation DELETE endpoint, i. e. "/delete"'), + '#default_value' => $config->get('deleteRoute'), + ]; + + $form['healthCheckRoute'] = [ + '#type' => 'textfield', + '#title' => $this->t('Deamon health check URL path'), + '#description' => $this->t('Provide the path to the health check GET endpoint, i. e. "/health-check"'), + '#default_value' => $config->get('healthCheckRoute'), ]; $form['usernameBlacklist'] = [ @@ -121,15 +128,16 @@ class WisskiCloudAccountManagerSettingsForm extends ConfigFormBase { public function submitForm(array &$form, FormStateInterface $form_state) { $config = $this->config('wisski_cloud_account_manager.settings'); - $config->set('daemonUrl', $form_state->getValue('daemonUrl')) - ->set('accountPostUrlPath', $form_state->getValue('accountPostUrlPath')) - ->set('allAccounts', $form_state->getValue('allAccounts')) - ->set('accountFilterByData', $form_state->getValue('accountFilterByData')) - ->set('accountProvisionAndValidationCheck', $form_state->getValue('accountProvisionAndValidationCheck')) - ->set('accountValidation', $form_state->getValue('accountValidation')) - ->set('usernameBlacklist', $form_state->getValue('usernameBlacklist')) + $config + ->set('daemonUrl', $form_state->getValue('daemonUrl')) + ->set('deleteRoute', $form_state->getValue('deleteRoute')) ->set('emailProviderBlacklist', $form_state->getValue('emailProviderBlacklist')) + ->set('healthCheckRoute', $form_state->getValue('healthCheckRoute')) + ->set('provisionRoute', $form_state->getValue('provisionRoute')) + ->set('usernameBlacklist', $form_state->getValue('usernameBlacklist')) ->set('subdomainBlacklist', $form_state->getValue('subdomainBlacklist')) + ->set('wsUrl', $form_state->getValue('wsUrl')) + ->set('wsToken', $form_state->getValue('wsToken')) ->save(); parent::submitForm($form, $form_state); diff --git a/src/Form/WisskiCloudAccountManagerValidationForm.php b/src/Form/WisskiCloudAccountManagerValidationForm.php new file mode 100755 index 0000000..9e8dd11 --- /dev/null +++ b/src/Form/WisskiCloudAccountManagerValidationForm.php @@ -0,0 +1,113 @@ +wisskiCloudAccountManagerDaemonApiActions = $wisskiCloudAccountManagerDaemonApiActions; + } + + /** + * Populate the reachable variables from services. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The class container. + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('wisski_cloud_account_manager.daemon_api.actions'), + ); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Do you really want to start WissKI Cloud validation?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return Url::fromRoute('wisski_cloud_account_manager.settings'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->t('This will start a WissKI Cloud account validation.'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Validate a WissKI Cloud account'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $aid = \Drupal::routeMatch()->getParameter('aid'); + $account = $this->wisskiCloudAccountManagerDaemonApiActions->getAccounts($aid)[0]; + $form['info'] = [ + '#type' => 'table', + '#header' => [ + $this->t('Account id'), + $this->t('Account name'), + $this->t('Account email'), + $this->t('Domain'), + + ], + '#rows' => [ + [ + $account['aid'], + $account['name'], + $account['mail'], + $account['subdomain'] . '.wisski.cloud', + ], + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $aid = \Drupal::routeMatch()->getParameter('aid'); + $this->wisskiCloudAccountManagerDaemonApiActions->validateAccount($aid); + $form_state->setRedirect('wisski_cloud_account_manager.manage'); + } + +} diff --git a/src/WisskiCloudAccountManagerDaemonApiActions.php b/src/WisskiCloudAccountManagerDaemonApiActions.php index 75b7148..113d5de 100755 --- a/src/WisskiCloudAccountManagerDaemonApiActions.php +++ b/src/WisskiCloudAccountManagerDaemonApiActions.php @@ -5,13 +5,17 @@ namespace Drupal\wisski_cloud_account_manager; use Symfony\Component\HttpFoundation\RequestStack; use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\Schema\Undefined; +use Drupal\Core\Database\Connection; 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; +use Drupal\Core\Template\TwigEnvironment; use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\user\Entity\User; use GuzzleHttp\ClientInterface; /** @@ -22,32 +26,19 @@ class WisskiCloudAccountManagerDaemonApiActions { use DependencySerializationTrait; /** - * The URL path to all account data GET endpoint. + * The admin email address. * * @var string */ - private string $ALL_ACCOUNTS = '/account/all'; + private string $ADMIN_EMAIL; /** - * The URL path to the POST endpoint. + * The URL path to provision PUT endpoint. * * @var string */ - private string $ACCOUNT_POST_URL_PART = '/account'; + private string $PROVISION_ROUTE; - /** - * The URL path to provision and validation GET endpoint. - * - * @var string - */ - private string $ACCOUNT_PROVISION_AND_VALIDATION_URL_PART; - - /** - * The URL path to provision and validation GET endpoint. - * - * @var string - */ - private string $ACCOUNT_VALIDATION_URL_PART = '/account/validation'; /** * The base URL of the WissKI Cloud account manager daemon. @@ -57,18 +48,34 @@ class WisskiCloudAccountManagerDaemonApiActions { private string $DAEMON_URL; /** - * The URL path to the filter by account data GET endpoint. + * The URL path to delete DELETE endpoint. * * @var string */ - private string $FILTER_BY_DATA_URL_PART = '/account/by_data'; + private string $DELETE_ROUTE; + /** - * The settings config. + * The database. * - * @var \Drupal\Core\Config\Config + * @var \Drupal\Core\Database\Connection */ - protected Config $settings; + protected Connection $database; + + /** + * The Route to the health check GET endpoint. + * + * @var string + */ + private string $HEALTH_CHECK_ROUTE; + + /** + * The Route to the info GET endpoint. + * + * @var string + * @todo not yet implemented + */ + private string $INFO_ROUTE = '/info'; /** * The HTTP client. @@ -77,6 +84,13 @@ class WisskiCloudAccountManagerDaemonApiActions { */ protected ClientInterface $httpClient; + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected LanguageManagerInterface $languageManager; + /** * The logger factory. * @@ -84,6 +98,13 @@ class WisskiCloudAccountManagerDaemonApiActions { */ protected LoggerChannelFactoryInterface $loggerFactory; + /** + * The mail manager. + * + * @var \Drupal\Core\Mail\MailManagerInterface + */ + protected MailManagerInterface $mailManager; + /** * The messenger service. * @@ -91,20 +112,6 @@ class WisskiCloudAccountManagerDaemonApiActions { */ protected MessengerInterface $messenger; - /** - * The language manager. - * - * @var \Drupal\Core\Language\LanguageManagerInterface - */ - protected LanguageManagerInterface $languageManager; - - /** - * The mail manager. - * - * @var \Drupal\Core\Mail\MailManagerInterface - */ - protected MailManagerInterface $mailManager; - /** * The request stack. * @@ -112,6 +119,15 @@ class WisskiCloudAccountManagerDaemonApiActions { */ protected RequestStack $requestStack; + + + /** + * The settings config. + * + * @var \Drupal\Core\Config\Config + */ + protected Config $settings; + /** * The string translation service. * @@ -119,61 +135,212 @@ class WisskiCloudAccountManagerDaemonApiActions { */ protected TranslationInterface $stringTranslation; + /** + * The Twig renderer. + * + * @var \Drupal\Core\Template\TwigEnvironment + */ + protected TwigEnvironment $twig; + /** * Class constructor. */ public function __construct( ConfigFactoryInterface $configFactory, + Connection $database, ClientInterface $httpClient, LanguageManagerInterface $languageManager, LoggerChannelFactoryInterface $loggerFactory, - MessengerInterface $messenger, MailManagerInterface $mailManager, + MessengerInterface $messenger, RequestStack $requestStack, TranslationInterface $stringTranslation, + TwigEnvironment $twig ) { // Services from container. $settings = $configFactory - ->getEditable('wisski_cloud_account_manager.settings'); + ->getEditable('wisski_cloud_account_manager.settings'); + $this->httpClient = $httpClient; + $this->database = $database; + $this->languageManager = $languageManager; + $this->loggerFactory = $loggerFactory; + $this->mailManager = $mailManager; + $this->messenger = $messenger; + $this->requestStack = $requestStack; $this->settings = $settings; $this->stringTranslation = $stringTranslation; - $this->loggerFactory = $loggerFactory; - $this->messenger = $messenger; - $this->httpClient = $httpClient; - $this->mailManager = $mailManager; - $this->requestStack = $requestStack; - $this->languageManager = $languageManager; + $this->twig = $twig; // 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->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->ACCOUNT_PROVISION_AND_VALIDATION_URL_PART = $settings->get('accountProvisionAndValidationUrlPart') ?: '/account/provision_and_validation'; - $this->ACCOUNT_VALIDATION_URL_PART = $settings->get('accountValidationUrlPart') ?: '/account/validation'; + $this->DAEMON_URL = $settings->get('daemonUrl') ?: 'http://wisski_cloud_api_daemon_app:2912/wisski-cloud-daemon/api/v1'; + $this->DELETE_ROUTE = $settings->get('deleteRoute') ?: '/delete'; + $this->PROVISION_ROUTE = $settings->get('provisionRoute') ?: '/provision'; + $this->ADMIN_EMAIL = \Drupal::config('system.site')->get('mail'); + $this->HEALTH_CHECK_ROUTE= $settings->get('healthCheckRoute') ?: '/health-check'; } /** - * Adds a new account to the WissKI Cloud account manager daemon. + * Adds a new account to the instance. + * + * First a new Drupal user is created. Additional data is + * stored in the wisski_cloud_account_manager_accounts table. * * @param array $account * The account to add. * * @return array - * The response from the daemon (account id with validation code). + * The account id with validation code. */ public function addAccount(array $account): array { try { - $request = [ - 'headers' => [ - 'Content-Type' => 'application/json', - ], - 'body' => json_encode($account), + + // Get current language. + $language = $this->languageManager->getCurrentLanguage()->getId(); + // Create Drupal user object. + $user = User::create(); + + // Mandatory. + $user->setPassword($account['password']); + $user->enforceIsNew(); + $user->setEmail($account['email']); + $user->setUsername($account['username']); // This username must be unique and accepts only string. It must be a minimum of two characters and can contain only lowercase letters, numbers, and underscores. + + // Optional. + $user->set('init', $account['email']); + $user->set('langcode', $language); + $user->set('preferred_langcode', $language); + $user->set('preferred_admin_langcode', $language); + + // Save user. + $result = $user->save(); + $validationCode = $this->generateValidationCode(); + + $database = $this->database; + + // Check if a record with this user ID already exists. + $query = $database->select('wisski_cloud_account_manager_accounts', 'w') + ->fields('w', ['uid']) + ->condition('w.uid', $user->id(), '='); + $result = $query->execute()->fetchField(); + + if ($result) { + throw new \Exception('A record with this user ID already exists.'); + } + $query = $database->insert('wisski_cloud_account_manager_accounts') + ->fields([ + 'uid' => $user->id(), + 'person_name' => $account['personName'], + 'organisation' => $account['organisation'], + 'subdomain' => $account['subdomain'], + 'validation_code' => $validationCode, + ]); + $query->execute(); + return [ + 'userId' => $user->id(), + 'username' => $account['username'], + 'personName' => $account['personName'], + 'email' => $account['email'], + 'subdomain' => $account['subdomain'], + 'validationCode' => $validationCode , ]; - $accountPostUrl = $this->DAEMON_URL . $this->ACCOUNT_POST_URL_PART; - $response = $this->httpClient->post($accountPostUrl, $request); - return json_decode($response->getBody() - ->getContents(), TRUE); + } + catch (\Exception $e) { + // Request failed, handle the error. + $this->loggerFactory + ->get('wisski_cloud_account_manager') + ->error('Can not create account: ' . $e->getMessage()); + $this->messenger + ->addError($this->stringTranslation->translate('Error creating account. See logs for details.')); + return []; + } + } + + /** + * Check for redundant account data. + * + * @param string $column + * The column to check. + * @param string $value + * The value to check. + */ + public function checkForRedundantAccountData(string $column, string $value) { + $database = \Drupal::database(); + $query = $database->select('wisski_cloud_account_manager_accounts', 'w'); + $query->fields('w', [$column]); + $query->condition('w.' . $column, $value, '='); + if ($query->execute()->fetchField()) { + return TRUE; + } + else { + return FALSE; + } + } + + /** + * Provisions an account in the WissKI Cloud account manager daemon. + * + * @param string $action + * The action to perform: create, delete, get. + * @param int $aid + * The account ID to provision. + * + * @return array + * The response from the daemon. + */ + public function crudInstance($action, $aid) { + try { + + $aid = trim($aid); + // Build the query string from the parameters. + $query_string = http_build_query([ + 'aid' => $aid, + ]); + + // Determine the route part depending on the action. + switch ($action) { + case 'create': + $restMethod = 'put'; + $routePart = $this->PROVISION_ROUTE; + break; + case 'delete': + $restMethod = 'delete'; + $routePart = $this->DELETE_ROUTE; + break; + default: + $restMethod = 'get'; + $routePart = $this->INFO_ROUTE; + break; + } + + // Combine the base URL and the query string. + $request_url = $this->DAEMON_URL . $routePart . '?' . $query_string; + // Send the GET request using the `drupal_http_request()` function. + $response = $this->httpClient->request($restMethod, $request_url); + // Check the response and handle the data accordingly. + if ($response->getStatusCode() == 200 || $response->getStatusCode() == 201) { + // Request successful, handle the data in $response->data. + $resultArray = json_decode($response->getBody()->getContents(), TRUE); + $this->messenger + ->addMessage($this->stringTranslation->translate('@message', ['@message' => $resultArray['message']])); + return $resultArray; + + } + if ($response->getStatusCode() == 404) { + // Request successful, handle the data in $response->data. + $resultArray = json_decode($response->getBody()->getContents(), TRUE); + $this->messenger + ->addError($this->stringTranslation->translate('@message', ['@message' => $resultArray['message']])); + return $resultArray; + } + else { + // Request failed, handle the error. + return [ + "message" => 'Request failed with code: ' . $response->getStatusCode(), + "data" => [], + 'success' => FALSE, + 'error' => $response->getBody()->getContents(), + ]; + } } catch (\Exception $e) { // Request failed, handle the error. @@ -181,60 +348,130 @@ class WisskiCloudAccountManagerDaemonApiActions { ->get('wisski_cloud_account_manager') ->error('Request failed with exception: ' . $e->getMessage()); $this->messenger - ->addError($this->stringTranslation->translate('Can not communicate with the WissKI Cloud account manager daemon. Try again later or contact cloud@wiss-ki.eu.')); + ->addError($this->stringTranslation->translate('Can not communicate with the WissKI Cloud account manager daemon. Try again later or contact @email.', + ['@email' + => $this->ADMIN_EMAIL])); return [ - "message" => 'Request failed with exception: ' . $e->getMessage(), - "data" => [ - 'email' => NULL, - 'validationCode' => NULL, - ], + "message" => 'Request failed with exception.', + "data" => [], 'success' => FALSE, + 'error' => $e->getMessage(), ]; - } } +} /** - * Check if an account with the given data already exists. + * Deletes an account from the WissKI Cloud account manager daemon. * - * @param array $dataToCheck - * The data to check. Possible keys are: - * - email - * - subdomain - * - username. + * @param int $uid + * The user ID of the account to delete. * * @return array * The response from the daemon. */ - public function checkAccountData(array $dataToCheck): array { + public function deleteAccount(int $aid): array { try { - // Build the query string from the parameters. - $query_string = http_build_query($dataToCheck); + $database = $this->database; + // Select the user ID from the accounts table. + $selectQuery = $database->select('wisski_cloud_account_manager_accounts', 'w') + ->fields('w', ['uid']) + ->condition('w.aid', $aid, '='); + $uid = $selectQuery->execute()->fetchField(); + + // Delete the account from the accounts table. + $deleteQuery = $database->delete('wisski_cloud_account_manager_accounts') + ->condition('aid', $aid, '='); + $deleteQuery->execute(); + + // Delete the user if exists. + $user = User::load($uid); + $user ? $user->delete() : NULL; + $this->messenger + ->addMessage($this->stringTranslation->translate('Account deleted successfully.')); + return [ + 'message' => 'Account deleted successfully.', + 'success' => TRUE, + ]; + } + catch (\Exception $e) { + // Request failed, handle the error. + $this->loggerFactory + ->get('wisski_cloud_account_manager') + ->error('Request failed with exception: ' . $e->getMessage()); + $this->messenger + ->addError($this->stringTranslation->translate('Something went wrong!' . $e->getMessage())); + } + } + + /** + * Generates a random validation code with 32 characters. + * + * @return string + * The generated validation code. + */ + function generateValidationCode() { + // Generate 16 random bytes and convert them to a 32 characters hexadecimal string + $code = bin2hex(random_bytes(16)); + return $code; + } + + /** + * Query accounts from the WissKI Cloud account manager daemon. + * + * @param int $aid + * The account ID to query. + * + * @return array[aid, name, mail, organisation, person_name, provisioned, status, subdomain, uid, validation_code] + * The accounts response from the daemon. + */ + public function getAccounts($aid = null): array { + try { + $query = $this->database->select('wisski_cloud_account_manager_accounts', 'w'); + $query->fields('w', ['aid', 'organisation', 'person_name', 'provisioned', 'subdomain', 'uid', 'validation_code']); + $query->leftjoin('users_field_data', 'u', 'w.uid = u.uid'); + $query->fields('u', ['name', 'mail', 'status']); + if ($aid) { + $query->condition('w.aid', $aid, '='); + } + $accounts = $query->execute()->fetchAll(\PDO::FETCH_ASSOC); + return $accounts; + } + catch (\Exception $e) { + // Request failed, handle the error. + $this->loggerFactory + ->get('wisski_cloud_account_manager') + ->error('Request failed with exception: ' . $e->getMessage()); + $this->messenger + ->addError($this->stringTranslation->translate('Can not communicate with the WissKI Cloud account manager daemon. Try again later or contact cloud@wiss-ki.eu.')); + return []; + } + } + + /** + * Checks if the WissKI Cloud account manager daemon is available. + * + * @return array[message:string, success:boolean] + * + */ + public function healthCheck() { + try { // 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->HEALTH_CHECK_ROUTE; // Send the GET request using the `drupal_http_request()` function. - $response = $this->httpClient->get($request_url); - + $response = $this->httpClient->request('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'], + "message" => "WissKI Cloud account manager daemon is available.", 'success' => TRUE, ]; } else { // Request failed, handle the error. return [ - "message" => 'Request failed with code: ' . $response->getStatusCode(), - "accountData" => [ - 'accountWithUsername' => NULL, - 'accountWithEmail' => NULL, - 'accountWithSubdomain' => NULL, - ], + "message" => 'WissKI Cloud account manager daemon is not available: ' . $response->getStatusCode(), 'success' => FALSE, ]; } @@ -243,90 +480,93 @@ class WisskiCloudAccountManagerDaemonApiActions { // Request failed, handle the error. $this->loggerFactory ->get('wisski_cloud_account_manager') - ->error('Request failed with exception: ' . $e->getMessage()); + ->error('Something went wrong: ' . $e->getMessage()); $this->messenger - ->addError($this->stringTranslation->translate('Can not communicate with the WissKI Cloud account manager daemon. Try again later or contact cloud@wiss-ki.eu.')); - return [ - "message" => 'Request failed with exception: ' . $e->getMessage(), - "accountData" => [ - 'accountWithUsername' => NULL, - 'accountWithEmail' => NULL, - 'accountWithSubdomain' => NULL, - ], - 'success' => FALSE, - ]; + ->addError($this->stringTranslation->translate('Can not communicate with the WissKI Cloud account manager daemon. Try again later or contact @adminMail.', + ['@adminMail' + => $this->ADMIN_EMAIL])); } } - /** - * Gets all accounts from the WissKI Cloud account manager daemon. - * + * Purges an account via the WissKI Cloud account manager daemon. + * Deletes the account from the accounts table and the Drupal user. + * Deletes the instance via the daemon from the WissKI Cloud. + * @param int $aid + * The account ID to purge. * @return array - * The accounts response from the daemon. + * The response from the daemon. */ - public function getAccounts(): array { + public function purgeAccount(int $aid) { try { - // Combine the base URL and the 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); - return json_decode($response->getBody()->getContents(), TRUE); + // Get the account ID from the route. + + // @todo Why is there a space in the account ID? + $aid = trim($aid); + + + // Delete the instance via the daemon from the WissKI Cloud. + $response = $this->crudInstance('delete', $aid); + if ($response['success']) { + // Delete the account and Drupal user. + $this->deleteAccount($aid); + $this->messenger + ->addMessage($this->stringTranslation->translate('Account purged successfully.')); + return [ + 'data' => NULL, + 'error' => NULL, + 'message' => 'Account purged successfully.', + 'success' => TRUE, + ]; + + } + else { + if (!$response['error']) { + // No success and no error. + $this->messenger + ->addMessage($this->stringTranslation->translate('Account not found or already purged.')); + return [ + 'data' => NULL, + 'error' => NULL, + 'message' => $response['message'], + 'success' => FALSE, + ]; + } + else { + // No success and error. + $this->messenger + ->addError($this->stringTranslation->translate('Something went wrong: ' . $response['message'])); + $this->loggerFactory->get('wisski_cloud_account_manager')->error($response['error']); + return [ + 'data' => NULL, + 'error' => $response['error'], + 'message' => 'Something went wrong: ' . $response['message'], + 'success' => FALSE, + ]; + } + } } + catch (\Exception $e) { // Request failed, handle the error. $this->loggerFactory ->get('wisski_cloud_account_manager') ->error('Request failed with exception: ' . $e->getMessage()); $this->messenger - ->addError($this->stringTranslation->translate('Can not communicate with the WissKI Cloud account manager daemon. Try again later or contact cloud@wiss-ki.eu.')); - return [ - "message" => 'Request failed with exception: ' . $e->getMessage(), - "accounts" => [], - 'success' => FALSE, - ]; + ->addError($this->stringTranslation->translate('Something went wrong!' . $e->getMessage())); } } - /** - * Checks the validation status of the given validation code. - * - * @param string $validationCode - * The validation code to check. - * - * @return array - * The account data from the daemon. - */ - public function validateAccount(string $validationCode): array { - try { - $url = $this->DAEMON_URL . $this->ACCOUNT_VALIDATION_URL_PART . '/' . $validationCode; - $validationResponse = $this->httpClient->put($url); - return json_decode($validationResponse->getBody() - ->getContents(), TRUE); - } - catch (\Exception $e) { - // Request failed, handle the error. - $this->loggerFactory - ->get('wisski_cloud_account_manager') - ->error('Request failed with exception: ' . $e->getMessage()); - $this->messenger - ->addError($this->stringTranslation->translate('Can not communicate with the WissKI Cloud account manager daemon. Try again later or contact cloud@wiss-ki.eu.')); - return [ - "message" => 'Request failed with exception: ' . $e->getMessage(), - "accounts" => [], - 'success' => FALSE, - ]; - } - } - - /** + /** * Sends a validation email to the given email address. * * @param string $email * The email address to send the validation email to. + * @param string $personName + * The person name to be used in the validation email. * @param string $validationCode * The validation code to be used in the validation link. */ - public function sendValidationEmail(string $email, string $validationCode): void { + public function sendValidationEmail(string $email, string $personName, string $validationCode): void { try { $module = 'wisski_cloud_account_manager'; $key = 'wisski_cloud_account_validation'; @@ -336,17 +576,19 @@ class WisskiCloudAccountManagerDaemonApiActions { $validationLink = $this->requestStack->getCurrentRequest() ->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); - if ($result['result'] === TRUE) { - $this->messenger - ->addMessage($this->stringTranslation->translate('Email send successfully.')); - } - else { - $this->messenger - ->addMessage($this->stringTranslation->translate('There was an error sending the email.'), 'error'); + $message = $this->twig->render('@wisski_cloud_account_manager/wisski-cloud-account-manager-validation-email.html.twig', [ + 'personName' => $personName, + 'validationLink' => $validationLink, + ]); + + $params['subject'] = $this->stringTranslation->translate('WissKI Cloud account validation'); + $params['message'] = Markup::create($message); + $result = $this->mailManager->mail($module, $key, $to, $langcode, $params, NULL, TRUE); + if ($result['result'] != TRUE) { + $this->loggerFactory + ->get('wisski_cloud_account_manager') + ->error('Email sending operation ended with error: ' . $result['message']); } } catch (\Exception $e) { @@ -360,4 +602,58 @@ class WisskiCloudAccountManagerDaemonApiActions { } + + /** + * Validates the account. + * + * @param string $validationCode + * The validation code to check. + * + * @return array [uid, name] + */ + public function validateAccount(string $validationCode): array { + try { + $selectQuery = $this->database->select('wisski_cloud_account_manager_accounts', 'w') + ->fields('w', ['aid', 'organisation', 'person_name', 'provisioned','subdomain', 'validation_code']) + ->condition('w.validation_code', $validationCode, '='); + $selectQuery->join('users_field_data', 'u', 'w.uid = u.uid'); + $selectQuery->fields('u', ['uid', 'name', 'mail', 'status']); + $account = $selectQuery->execute()->fetchAll(\PDO::FETCH_ASSOC); + if (isset($account[0]['status'])) { + if ($account[0]['status'] == 0) { + $updateQuery = $this->database->update('users_field_data') + ->fields(['status' => 1]) + ->condition('uid', $account['0']['uid'], '='); + $updateQuery->execute(); + $account = $selectQuery->execute()->fetchAll(\PDO::FETCH_ASSOC); + $this->messenger + ->addMessage($this->stringTranslation->translate('Account validated successfully.')); + + } else { + $this->messenger + ->addMessage($this->stringTranslation->translate('Account already validated.')); + } + return [ + 'uid' => $account[0]['uid'], + 'name' => $account[0]['name']]; + } + else { + $this->messenger + ->addError($this->stringTranslation->translate('Account validation failed. Please contact @adminEmail.', + ['@adminEmail' + => $this->ADMIN_EMAIL])); + return []; + } + } + catch (\Exception $e) { + // Request failed, handle the error. + $this->loggerFactory + ->get('wisski_cloud_account_manager') + ->error('Request failed with exception: ' . $e->getMessage()); + $this->messenger + ->addError($this->stringTranslation->translate('Can not communicate with the WissKI Cloud account manager daemon. Try again later or contact cloud@wiss-ki.eu.')); + return []; + } + } + } 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 588cf96..d96ee2a 100755 --- 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,88 @@ {# wisski_cloud_account_manager/templates/wisski_cloud_account_manager_account_managing_page.html.twig #}{{ healthCheck.message }}
+| Id | -Person name | -Username | -Subdomain | -Valid | -Provisioned | -Options | +AID | +UID | +Person name | +Organisation | +Username | +Subdomain | +Valid | +Provisioned | +Options | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ item._id }} | -{{ item.personName }} | -{{ item.email }} | -{{ item.username }} | -{{ item.subdomain }} | +|||||||||||||
| {{ item.aid }} | +{{ item.uid }} {% if item.status is null %}{% endif %} | +{{ item.person_name }} | +{{ item.organisation }} | ++ {{ item.mail }} + | +{{ item.name }} | ++ {% if item.provisioned == 2 %} + {{ item.subdomain }}.wisski.cloud + {% else %} + {{ item.subdomain }}.wisski.cloud + {% endif %} + | - {% if item.valid == 1 %} + {% if item.status is same as("1") %} yes - {% elseif item.valid == 0 %} + {% elseif item.status is same as("0") %} no {% else %} unknown {% endif %} | -{% if item.provisioned == 1 %} - ongoing + |
+ {% if item.provisioned == 1 %}
+
+ ongoing
+
{% elseif item.provisioned == 2 %}
yes
- {% elseif item.provisioned == 3 %}
+ {% elseif item.provisioned == 0 %}
no
{% else %}
- unknown
+
+ unknown
+
+
{% endif %} |
- - |
No accounts found. Go get some friends.
{% endif %} diff --git a/templates/wisski-cloud-account-manager-health-check-page.html.twig b/templates/wisski-cloud-account-manager-health-check-page.html.twig new file mode 100755 index 0000000..aaf867b --- /dev/null +++ b/templates/wisski-cloud-account-manager-health-check-page.html.twig @@ -0,0 +1,27 @@ +{# wisski_cloud_account_manager/templates/wisski_cloud_account_manager_validation_page.html.twig #} +| Check | +Values | +Test | +Result | +
|---|---|---|---|
| healthCheck | +- | ++ + | ++ |
Hi {{ personName }},
+Thank you for registering to our site.
+Please validate your account by clicking on this + link or copy this to the address bar of your browser: +
+{{ validationLink }}
+You have 24 hours to validate your account.
+| Person name | -Username | -Subdomain | -Valid | -Provisioned | +Account ID | +Person name | +Organisation | +Username | +Subdomain | +Valid | +Provisioned | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ account.data.personName }} | -{{ account.data.email }} | -{{ account.data.username }} | -{{ account.data.subdomain }} | +{{ account.aid }} | +{{ account.person_name }} | +{{ account.organisation }} | +{{ account.mail }} | +{{ account.name }} | +{{ account.subdomain }} | - {% if account.data.valid == 1 %} + {% if account.status == 1 %} yes - {% elseif account.data.valid == 0 %} + {% elseif account.status == 0 %} no {% else %} unknown {% endif %} | -{% if account.data.provisioned == 1 %} + | {% if account.provisioned == 1 %} ongoing - {% elseif account.data.provisioned == 2 %} + {% elseif account.provisioned == 2 %} yes - {% elseif account.data.provisioned == 3 %} - failed - {% else %} + {% elseif account.provisioned == 3 %} unknown {% endif %} |
Your account is valid and provisioned. You can now log in to your account at https://{{ account.data.subdomain }}.wisski.cloud.
- {% elseif account.data.valid == 1 and (account.data.provisioned == 0 or account.data.provisioned == 3)%} -Your account is valid but the provision failed or the state is unknown. Please refresh this site and if the state persists contact cloud@wiss-ki.eu to resolve this issue.
- {% elseif account.data.valid == 1 and account.data.provisioned == 1 %} -Your account is valid and the provision of your WissKI Cloud instance has started. Please wait a few minutes and refresh this page.
- {% elseif account.data.valid == 0 %} -Your account is not valid. Please contact info@wiss-ki.eu to resolve this issue.
+Your account is valid and provisioned. You can now log in to your account at https://{{ account.subdomain }}.wisski.cloud.
+ {% elseif account.status == 1 and (account.provisioned == 0 or account.provisioned == 3)%} +Your account is valid but the provision failed or the state is unknown. Please refresh this site and if the state persists contact cloud@wiss-ki.eu to resolve this issue.
+ {% elseif account.status == 1 and account.provisioned == 1 %} +Your account is valid and the provision of your WissKI Cloud instance has started. Please wait a few minutes and refresh this page.
+ {% elseif account.status == 0 %} +Your account is not valid. Please contact info@wiss-ki.eu to resolve this issue.
{% else %} -Something went wrong. Please contact info@wiss-ki.eu to resolve this issue.
+Something went wrong. Please contact info@wiss-ki.eu to resolve this issue.
{% endif %}