tapis_system-1.4.1-alpha2/tapis_system.module

tapis_system.module
<?php

/**
 * @file
 * Provides a system entity type.
 */

use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\node\NodeForm;
use Drupal\node\NodeInterface;
use Drupal\tapis_system\DrupalIds;
use Drupal\tapis_system\Exception\TapisSystemException;
use Drupal\tapis_system\TapisSystemCredentialInterface;
use Drupal\user\UserInterface;
use phpseclib3\Crypt\RSA;

/**
 * Implements hook_help().
 */
function tapis_system_help($route_name, RouteMatchInterface $route_match)
{
  switch ($route_name) {
      // Main module help for the tapis_system module.
    case 'help.page.tapis_system':
      return '<p>' . t('This module integrates Tapis systems & system credentials within Drupal using the Tapis System content type and System Credential entity, respectively. For systems with batch schedulers (e.g., SLURM, etc.), this module also provides a System scheduler profile content type, which lets you customize a system\'s scheduler options, such as the modules to load, etc. Furthermore, this module also defines a Batch Logical Queue Storage entity, which can be used for defining different HPC queues as part of a batch system\'s definition.') . '</p>';
  }
}

/**
 * Implements hook_theme().
 */
function tapis_system_theme()
{
  return ['tapis_system_credential' => ['render element' => 'elements']];
}

/**
 * Alters the system credential entity's view so that it includes a button to download the system credential's public key and a form to test the system credential.
 * 
 * @param array $build
 * @param EntityInterface $entity
 * @param EntityViewDisplayInterface $display
 * @param string $view_mode
 */
function tapis_system_tapis_system_credential_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode)
{
  /** @var \Drupal\tapis_system\TapisSystemCredentialInterface $entity */
  $download_system_credentials_url = Url::fromRoute("tapis_system.tapis_system_credential_download", ['tapis_system_credential' => $entity->id()]);

  // Add a button-styled link button to the entity build, titled "Download SSH Keypair", that links to $download_system_credentials_url.
  // Make sure it opens in a new tab.
  // Add some space to the top of the button.
  $build['download_button'] = [
    '#type' => 'link',
    '#title' => t('Download SSH public key'),
    '#url' => $download_system_credentials_url,
    '#weight' => 100,
    '#attributes' => [
      'class' => ['button'],
      'target' => '_blank',
      'style' => 'margin-top: 20px;',
    ],
  ];
  $build['download_message'] = [
    '#markup' => '<p>' . t("Please ensure that this credential's public key has been added to your system account's authorized_keys file.") . '</p>',
    '#weight' => 101,
  ];

  // Embed our test system credentials form into the entity build.
  // Only if the current user has the "administer tapis system" permission, or they are the system credential's owner
  $currentUser = \Drupal::currentUser();
  $userPermissionForSystemAdmin = $currentUser->hasPermission('administer tapis system');
  $userPermissionForEditAnyContent = $currentUser->hasPermission('edit any tapis_system content');
  if ($userPermissionForSystemAdmin || $userPermissionForEditAnyContent ||
    $entity->getOwnerId() === $currentUser->id()) {
    $systemId = $entity->getSystemId();
    $system = Node::load($systemId);
    // $build['test_credentials_form'] = \Drupal::formBuilder()->getForm('Drupal\tapis_system\Form\TestTapisSystemCredentialForm', $system);
    // Get the form builder service.
    $formBuilder = \Drupal::service('form_builder');

    // Build the form using the form builder service.
    $form = $formBuilder->getForm('Drupal\tapis_system\Form\TestTapisSystemCredentialForm', $system);

    // Add the form to the entity build.
    $build['test_credentials_form'] = $form;
    $build['test_credentials_form']['#weight'] = 102;

  }
}

/**
 * Ajax callback function for the "Test system credentials" button.
 * 
 * @param array $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 */
function tapis_system_test_credentials_callback(array &$form, \Drupal\Core\Form\FormStateInterface $form_state)
{
  $form_state->setRebuild(TRUE);
  return $form['test_credentials_result'];
}

/**
 * Implements hook_ENTITY_TYPE_insert() to display a message to the user on next steps, once they have created a system credential entity
 * 
 * @param TapisSystemCredentialInterface $entity
 */
function tapis_system_tapis_system_credential_insert(TapisSystemCredentialInterface $entity)
{
  $tapisSystem = \Drupal::entityTypeManager()->getStorage('node')->load($entity->getSystemId());
  $tapisSystemHost = $tapisSystem->get(DrupalIds::SYSTEM_HOST)->getValue()[0]['value'];
  /**
   * Create a message for the user to copy the public key to the system (using the Drupal messenger service)
   * Message:
   * Follow the steps below to complete the setup:
   * 1. Login manually to this system at <hostname> 
   * 2. Append the public key displayed below to your ~/.ssh/authorized_keys file
   * 3. Verify system access by clicking Check system access button.
   */
  $message = t('Follow the steps below to complete the setup:<br/><br/>1. Manually login to this system at @host<br/>2. Append the public key displayed below to your ~/.ssh/authorized_keys file<br/>3. Verify system access by clicking Check system access button.', [
    '@host' => $tapisSystemHost,
  ]);
  \Drupal::messenger()->addMessage($message);
}

/**
 * Updates the system node's and scheduler profile's view to include a form to test system access and display a message if the system's/scheduler profile's tenant is in maintenance mode.
 * 
 * @param array $build
 * @param EntityInterface $entity
 * @param EntityViewDisplayInterface $display
 * @param string $view_mode
 */
function tapis_system_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode)
{
  /** @var \Drupal\node\NodeInterface $entity */
  if (
    !($entity->getEntityTypeId() === 'node' &&
      ($entity->bundle() === DrupalIds::NODE_BUNDLE_SYSTEM
        || $entity->bundle() === DrupalIds::NODE_BUNDLE_SYSTEM_SCHEDULER_PROFILE))
  ) {
    return;
  }

  /** @var Drupal\tapis_tenant\TapisProvider\TapisSiteTenantProviderInterface $tapisSiteTenantProvider */
  $tapisSiteTenantProvider = \Drupal::service("tapis_tenant.tapis_site_tenant_provider");
  $tapisSiteTenantProvider->setIgnoreMaintenanceMode(TRUE);

  if ($entity->bundle() === DrupalIds::NODE_BUNDLE_SYSTEM) {
    // Get the system's tenant id
    $tenantId = $entity->get(DrupalIds::SYSTEM_TENANT)->first()->getValue()['target_id'];

    // Check if the site is in maintenance mode
    if ($tapisSiteTenantProvider->isTenantInMaintenanceMode($tenantId)) {
      \Drupal::messenger()->addMessage(t("This resource is currently in maintenance mode."));
    }

    // Embed our test system credentials form into the entity build.
    // $build['test_credentials_form'] = \Drupal::formBuilder()->getForm('Drupal\tapis_system\Form\TestTapisSystemCredentialForm', $entity);
    // Get the form builder service.
    $formBuilder = \Drupal::service('form_builder');

    // Build the form using the form builder service.
    $form = $formBuilder->getForm('Drupal\tapis_system\Form\TestTapisSystemCredentialForm', $entity);

    // Add the form to the entity build.
    $build['test_credentials_form'] = $form;
    $build['test_credentials_form']['#weight'] = 1000;


  } else {
    // Get the system scheduler profile's tenant id
    $tenantId = $entity->get(DrupalIds::SYSTEM_SCHEDULER_PROFILE_TENANT)->first()->getValue()['target_id'];

    // Check if the site is in maintenance mode
    if ($tapisSiteTenantProvider->isTenantInMaintenanceMode($tenantId)) {
      \Drupal::messenger()->addMessage(t("This resource is currently in maintenance mode."));
    }
  }
}

/**
 * Implements hook_user_cancel().
 * 
 * When a user is deleted, we want to delete any system credentials that they own.
 * 
 * @param array $edit
 * @param UserInterface $account
 * @param string $method
 */
function tapis_system_user_cancel($edit, UserInterface $account, $method)
{
  switch ($method) {
    case 'user_cancel_reassign':
      // Delete system credentials.
      $storage = Drupal::entityTypeManager()->getStorage('tapis_system_credential');
      $tapis_system_credential_ids = $storage->getQuery()->accessCheck(FALSE)->condition('uid', $account->id())->execute();
      foreach ($storage->loadMultiple($tapis_system_credential_ids) as $tapis_system_credential) {
        $tapis_system_credential->delete();
      }
      break;
  }
}

/**
 * Implements hook_ENTITY_TYPE_predelete() for user entities.
 * 
 * When a user is deleted, we want to delete any system credentials that they own.
 */
function tapis_system_user_predelete(UserInterface $account)
{
  // Delete system credentials.
  $storage = Drupal::entityTypeManager()->getStorage('tapis_system_credential');
  $tapis_system_credential_ids = $storage->getQuery()->accessCheck(FALSE)->condition('uid', $account->id())->execute();
  $tapis_system_credentials = $storage->loadMultiple($tapis_system_credential_ids);
  $storage->delete($tapis_system_credentials);
}

/**
 * Prepares variables for system credential templates.
 *
 * Default template: tapis-system-credential.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the system credential information
 *     and any fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_tapis_system_credential(array &$variables)
{
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Deletes scheduler profiles from Tapis upon deletion from Drupal.
 * Also deletes all system credentials for a system when that system is deleted from Drupal.
 * 
 * @param NodeInterface $entity
 */
function tapis_system_node_predelete(NodeInterface $entity)
{
  $bundle = $entity->bundle();

  if ($bundle === DrupalIds::NODE_BUNDLE_SYSTEM) {
    // Delete all system credentials for this tapis system
    $systemCredentialsStorage = \Drupal::entityTypeManager()->getStorage('tapis_system_credential');

    $systemCredentialIdsToDelete = $systemCredentialsStorage->getQuery()
      ->accessCheck(FALSE)
      ->condition('system', $entity->id())
      ->execute();

    foreach ($systemCredentialIdsToDelete as $systemCredentialId) {
      $systemCredentialsStorage->load($systemCredentialId)->delete();
    }
  } else if ($bundle === DrupalIds::NODE_BUNDLE_SYSTEM_SCHEDULER_PROFILE) {
    // Get the tenant id.
    if ($entity->get(DrupalIds::SYSTEM_SCHEDULER_PROFILE_TENANT)->first() !== NULL) {
      $tenantId = $entity->get(DrupalIds::SYSTEM_SCHEDULER_PROFILE_TENANT)->first()->getValue()['target_id'];
      /** @var Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider */
      $tapisSystemProvider = \Drupal::service("tapis_system.tapis_system_provider");

      $tapisSchedulerProfileDefinition = $tapisSystemProvider->convertSchedulerProfileToJSON($entity);
      $tapisSystemProvider->deleteSchedulerProfile($tenantId, $tapisSchedulerProfileDefinition['name']);
    }
  }
}

function tapis_system_menu_local_tasks_alter(&$data, $route_name)
{
  /** @var \Drupal\node\Entity\Node $node */
  $node = \Drupal::routeMatch()->getParameter('node');
  if ($node) {
    if ($node->bundle() !== DrupalIds::NODE_BUNDLE_SYSTEM) {
      // hide the share/unshare form stuff
      foreach ($data['tabs'][0] as $key => $value) {
        // if $key starts with 'entity.tapis_system.' then unset it
        if (strpos($key, 'entity.tapis_system') === 0) {
          unset($data['tabs'][0][$key]);
        }
      }
    }
  }
}

/**
 * Saves systems and scheduler profiles in Tapis upon saving them in Drupal
 * 
 * @param NodeInterface $entity
 */
function tapis_system_node_presave(NodeInterface $entity)
{
  $bundle = $entity->bundle();

  if ($bundle !== DrupalIds::NODE_BUNDLE_SYSTEM && $bundle !== DrupalIds::NODE_BUNDLE_SYSTEM_SCHEDULER_PROFILE) {
    return;
  }

  /** @var Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider */
  $tapisSystemProvider = \Drupal::service("tapis_system.tapis_system_provider");

  if ($bundle === DrupalIds::NODE_BUNDLE_SYSTEM_SCHEDULER_PROFILE) {
    // Get the tenant id.
    $tenantId = $entity->get(DrupalIds::SYSTEM_SCHEDULER_PROFILE_TENANT)->first()->getValue()['target_id'];

    if (!$entity->original) {
      // This is a new scheduler profile.
      // Update Tapis via API call.
      $tapisSchedulerProfileDefinition = $tapisSystemProvider->convertSchedulerProfileToJSON($entity);
      // dpm($tapisSchedulerProfileDefinition);
      $tapisSystemProvider->createSchedulerProfile($tenantId, $tapisSchedulerProfileDefinition);
    } else {
      $schedProfOwnerId = $entity->getOwnerId();
      // This is an existing scheduler profile.
      // Delete it and create a new one, since Tapis doesn't provide an update endpoint.
      $tapisSchedulerProfileDefinition = $tapisSystemProvider->convertSchedulerProfileToJSON($entity, $schedProfOwnerId);
      $tapisSystemProvider->deleteSchedulerProfile($tenantId, $tapisSchedulerProfileDefinition['name'], $schedProfOwnerId);
      $tapisSystemProvider->createSchedulerProfile($tenantId, $tapisSchedulerProfileDefinition, $schedProfOwnerId);
    }
    return;
  }

  // Get the system owner id.
  // $systemOwnerUid = $entity->get(DrupalIds::SYSTEM_OWNER)->getValue()[0]['target_id'];
  // $systemOwnerUid = $entity->get('tapis_system_owner')->getValue()[0]['target_id'];
  if (isset($entity->get(DrupalIds::SYSTEM_OWNER)->getValue()[0]['target_id'])) {
    $systemOwnerUid = $entity->get(DrupalIds::SYSTEM_OWNER)->getValue()[0]['target_id'];
  } else {
    $systemOwnerUid = \Drupal::currentUser()->id();
  }

  // Get the tenant id.
  $tenantId = $entity->get(DrupalIds::SYSTEM_TENANT)->first()->getValue()['target_id'];

  $isPublished = $entity->isPublished();
  if (!$isPublished) return; // Only create/update Tapis systems if the system is published.

  $tapisSystemId = $entity->get(DrupalIds::SYSTEM_TAPIS_ID)->getValue()[0]['value'];
  $doesSystemExist = $tapisSystemProvider->doesSystemIdExist($tenantId, $tapisSystemId, $systemOwnerUid);

  if (!$doesSystemExist) {
    // This is a new Tapis system.

    // Set the system's owner to be the current user.
    $entity->set(DrupalIds::SYSTEM_OWNER, ["target_id" => \Drupal::currentUser()->id()]);

    // Update Tapis via API call.
    $tapisSystemDefinition = $tapisSystemProvider->convertSystemToJSON($entity);

    $tapisSystemProvider->createSystem($tenantId, $tapisSystemDefinition);

    // check if the system uses a static system user
    $use_static_system_user = boolval($entity->get(DrupalIds::SYSTEM_USE_STATIC_SYSTEM_USER)->getValue()[0]['value']);
    if ($use_static_system_user) {
      // In this case, we need the user to create a system credential for the static system user
      // \Drupal::messenger()->addMessage(t('The system was created successfully. You must add a user credential for this system. <a class="btn btn-default button button--primary" href=":url">Add user credential</a>', [':url' => '/tapis/system-credential/add']));
      \Drupal::messenger()->addMessage(t('The system was created successfully. You must add a user credential for this system.'));
    }
  } else {
    // This is an existing Tapis system.
    // Update Tapis via API call.
    $tapisSystemId = $entity->get(DrupalIds::SYSTEM_TAPIS_ID)->getValue()[0]['value'];

    // Check if the system's enabled field has changed between the original and the new entity.
    $enabledChanged = boolval($entity->get(DrupalIds::SYSTEM_ENABLED)->getValue()[0]['value']) !== boolval($entity->original->get(DrupalIds::SYSTEM_ENABLED)->getValue()[0]['value']);
    if ($enabledChanged) {
      if (boolval($entity->get(DrupalIds::SYSTEM_ENABLED)->getValue()[0]['value'])) {
        // The system was enabled.
        $tapisSystemProvider->enableSystem($tenantId, $tapisSystemId, $systemOwnerUid);
        \Drupal::messenger()->addMessage(t('The system was enabled successfully.'));
      } else {
        // The system was disabled.
        $tapisSystemProvider->disableSystem($tenantId, $tapisSystemId, $systemOwnerUid);
        \Drupal::messenger()->addMessage(t('The system was disabled successfully.'));
      }
    }
    $tapisSystemDefinition = $tapisSystemProvider->convertSystemToJSON($entity);
    $tapisSystemProvider->updateSystem($tenantId, $tapisSystemId, $tapisSystemDefinition, $systemOwnerUid);
  }
}

/**
 * Implements hook_form_alter to add a validation handler to the system credential creation form.
 * 
 * This validation handler will check if the user already has a system credential for the system they are trying to create a new system credential for, and if so, display an error message and prevent the form from being submitted.
 * 
 * @param array $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 * @param string $form_id
 */
function tapis_system_form_alter(&$form, $form_state, $form_id)
{
  if ($form_id === "tapis_system_credential_add_form") {
    // add "tapis_system_credential_form_validation_handler" as a validation handler for this form
    $form['#validate'][] = 'tapis_system_credential_form_validation_handler';
  }
}

/**
 * In this form submit handler, check if the user already has a system credential for the system they are trying to create a new system credential for, and if so, display an error message and prevent the form from being submitted
 * 
 * @param array $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 * */
function tapis_system_credential_form_validation_handler(&$form, $form_state)
{
  if (!isset($form_state->getValue('system')[0]['target_id'])) {
    return;
  }
  $systemId = $form_state->getValue('system')[0]['target_id'];
  // Username entered on the form.
  $loginUser = NULL;
  if (isset($form_state->getValue('loginUser')[0]['value'])) {
    $loginUser = $form_state->getValue('loginUser')[0]['value'];
  }
  // If username not provided and selected system is static, use effective user.
  if (empty($loginUser)) {
    $system = \Drupal::entityTypeManager()->getStorage('node')->load($systemId);
    if ($system) {
      $use_static = FALSE;
      $f = $system->get(DrupalIds::SYSTEM_USE_STATIC_SYSTEM_USER);
      if ($f && $f->first()) {
        $use_static = (bool) ($f->first()->getValue()['value'] ?? FALSE);
      }
      if ($use_static) {
        $loginUser = $system->get(DrupalIds::SYSTEM_EFFECTIVE_USER)->getValue()[0]['value'] ?? NULL;
      }
    }
  }
  $systemCredentialsStorage = \Drupal::entityTypeManager()->getStorage('tapis_system_credential');
  $systemCredentialIds = $systemCredentialsStorage->getQuery()
    ->accessCheck(FALSE)
    ->condition('system', $systemId)
    ->condition('loginUser', $loginUser)
    ->execute();

  if (count($systemCredentialIds) > 0) {
    // A system credential entity for the same user id and system id already exists.
    // We don't want to create a new system credential in Tapis. Error out with
    // a clear message about system+username duplicates.
    $error_message = t('An SSH credential for this system and username already exists. Please remove existing SSH credentials to replace with another.');
    $form_state->setErrorByName('system', $error_message);
    $form_state->setRebuild();
    return;
  }
}

/**
 * Saves system credentials w/ Tapis
 * 
 * @param EntityInterface $entity
 */
function tapis_system_tapis_system_credential_presave(EntityInterface $entity)
{
  /** @var \Drupal\tapis_system\TapisSystemCredentialInterface $entity */

  /** @var Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider */
  $tapisSystemProvider = \Drupal::service("tapis_system.tapis_system_provider");

  if (!$entity->original) {
    // This is a new Tapis System credential, so call Tapis API.

    // First, check Drupal if a system credential entity for the same user id and system id already exists.
    $systemId = $entity->getSystemId();
    $loginUser = $entity->get('loginUser')->getValue()[0]['value'] ?? NULL;
    $systemCredentialsStorage = \Drupal::entityTypeManager()->getStorage('tapis_system_credential');
    $systemCredentialIds = $systemCredentialsStorage->getQuery()
      ->accessCheck(FALSE)
      ->condition('system', $systemId)
      ->condition('loginUser', $loginUser)
      ->execute();

    if (count($systemCredentialIds) > 0) {
      // A system credential entity for the same user id and system id already exists.
      // We don't want to create a new system credential in Tapis.
      // Throw an error with a clear message about system+username duplicates.
      throw new TapisSystemException('An SSH credential for this system and username already exists. Please remove existing SSH credentials to replace with another.');
    }

    // @todo Note: we don't call Tapis API for entity updates -- only entity creations + deletions.
    $tapisDefinition = $entity->getTapisDefinition();
    $system = \Drupal::entityTypeManager()->getStorage("node")->load($systemId);

    // Get the tenant id.
    $tenantId = $system->get(DrupalIds::SYSTEM_TENANT)->first()->getValue()['target_id'];

    // Create the system credential in Tapis.
    $tapisSystemProvider->createUserCredential(
      $tenantId,
      $tapisDefinition['systemId'],
      $tapisDefinition['systemOwnerUid'],
      $tapisSystemProvider->getEffectiveUserForSystem($system),
      $tapisDefinition['accessorUserId'],
      $tapisDefinition['accessorLoginUser'],
      $tapisDefinition['privateKey'],
      $tapisDefinition['publicKey'],
      $tapisDefinition['skipCredentialCheck']
    );
  }
}

/**
 * Generates an SSH keypair
 * 
 * @param string $comment
 */
function tapis_system_generate_ssh_keypair($comment = NULL)
{
  $privateKey = RSA::createKey(4096);

  // Save keys to files
  if ($comment) {
    $publicKey = $privateKey->getPublicKey()->toString('OpenSSH', [
      'comment' => $comment
    ]);
  } else {
    $publicKey = $privateKey->getPublicKey()->toString('OpenSSH');
  }

  return [
    'private_key' => $privateKey->toString('PKCS1'),
    'public_key' => $publicKey,
  ];
}

/**
 * Deletes system credentials from Tapis upon deletion from Drupal.
 * 
 * @param EntityInterface $entity
 */
function tapis_system_tapis_system_credential_predelete(EntityInterface $entity)
{
  /** @var \Drupal\tapis_system\TapisSystemCredentialInterface $entity */

  /** @var Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider */
  $tapisSystemProvider = \Drupal::service("tapis_system.tapis_system_provider");

  $systemId = $entity->getSystemId();
  $system = \Drupal::entityTypeManager()->getStorage("node")->load($systemId);

  // Get the tenant id.
  $tenantId = $system->get(DrupalIds::SYSTEM_TENANT)->first()->getValue()['target_id'];

  $systemTapisId = $system->get(DrupalIds::SYSTEM_TAPIS_ID)->getValue()[0]['value'];
  $systemOwnerId = $system->get(DrupalIds::SYSTEM_OWNER)->getValue()[0]['target_id'];

  $systemEffectiveUserId = $tapisSystemProvider->getEffectiveUserForSystem($system);
  $tapisSystemProvider->removeUserCredential($tenantId, $systemTapisId, $systemOwnerId, $systemEffectiveUserId, \Drupal::currentUser()->id());
}

/**
 * Defines operations that can be taken on systems
 * 
 * @param \Drupal\node\Entity\Node $entity
 */
function tapis_system_entity_operation(EntityInterface $entity)
{
  if ($entity->getEntityTypeId() !== "node" || $entity->bundle() !== DrupalIds::NODE_BUNDLE_SYSTEM) {
    return;
  }
  $operations = [];

  $currentUser = \Drupal::currentUser();
  $userPermissionForSystemAdmin = $currentUser->hasPermission('administer tapis system');
  $userPermissionForEditAnyContent = $currentUser->hasPermission('edit any tapis_system content');
  if ($userPermissionForSystemAdmin || $userPermissionForEditAnyContent ||
    $currentUser->id() === $entity->getOwnerId()) {
    $operations['changeOwner'] = [
      'title' => t('Change owner'),
      'url' => Url::fromRoute('tapis_system.change_system_owner', ['node' => $entity->id()]),
      'weight' => 102,
    ];
  }
  return $operations;
}

/**
 * Add our custom validation handler for system forms
 * 
 * @param array $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 */
function tapis_system_form_node_form_alter(&$form, &$form_state)
{
  // $node = $form_state->getFormObject()->getEntity();
  $nodeObject = $form_state->getFormObject();
  /** @var NodeForm $nodeObject */
  $node = $nodeObject->getEntity();

  if ($node->bundle() === DrupalIds::NODE_BUNDLE_SYSTEM) {
    // On create, if a Tapis tenant is available, select the first as default.
    if ($node->isNew() && isset($form[DrupalIds::SYSTEM_TENANT])) {
      $has_current = FALSE;
      // Detect any current selection provided by user input or defaults.
      $current_val = $form_state->getValue(DrupalIds::SYSTEM_TENANT);
      if (!empty($current_val)) {
        $has_current = TRUE;
      }
      elseif (isset($form[DrupalIds::SYSTEM_TENANT]['widget'][0]['target_id']['#default_value'])
        && !empty($form[DrupalIds::SYSTEM_TENANT]['widget'][0]['target_id']['#default_value'])) {
        $has_current = TRUE;
      }
      elseif (isset($form[DrupalIds::SYSTEM_TENANT]['widget']['#default_value'])
        && !empty($form[DrupalIds::SYSTEM_TENANT]['widget']['#default_value'])) {
        $has_current = TRUE;
      }

      if (!$has_current) {
        // Query first published tenant node (bundle: tapis_tenant).
        $tenant_nids = \Drupal::entityQuery('node')
          ->accessCheck(FALSE)
          ->condition('type', 'tapis_tenant')
          ->condition('status', 1)
          ->range(0, 1)
          ->execute();
        if (!empty($tenant_nids)) {
          $first_tenant_nid = reset($tenant_nids);
          // Apply default depending on widget structure (options_select/autocomplete).
          if (isset($form[DrupalIds::SYSTEM_TENANT]['widget'][0]['target_id'])) {
            $form[DrupalIds::SYSTEM_TENANT]['widget'][0]['target_id']['#default_value'] = $first_tenant_nid;
          }
          elseif (isset($form[DrupalIds::SYSTEM_TENANT]['widget'])) {
            $form[DrupalIds::SYSTEM_TENANT]['widget']['#default_value'] = $first_tenant_nid;
          }
        }
      }
    }
    // $node = $form_state->getFormObject()->getEntity();

    // Add System restrictions (Tenant sharing) radio on edit form.
    // Show only to owner or admins and only for existing systems.
    $currentUser = \Drupal::currentUser();
    $userPermissionForSystemAdmin = $currentUser->hasPermission('administer tapis system');
    $userPermissionForEditAnyContent = $currentUser->hasPermission('edit any tapis_system content');
    $canConfigureSharing = ($userPermissionForSystemAdmin || $userPermissionForEditAnyContent || ($currentUser->id() === $node->getOwnerId()));

    if ($canConfigureSharing) {
      // Default selection based on Tapis system public state.
      $defaultRestriction = 'private';
      if (!$node->isNew()) {
        try {
          /** @var \Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider */
          $tapisSystemProvider = \Drupal::service('tapis_system.tapis_system_provider');
          $tenantId = $node->get(DrupalIds::SYSTEM_TENANT)->first()->getValue()['target_id'];
          $tapisId = $node->get(DrupalIds::SYSTEM_TAPIS_ID)->first()->getValue()['value'];
          $systemOwner = $node->getOwnerId();
          $system = $tapisSystemProvider->getSystem($tenantId, $tapisId, $systemOwner);
          if (!empty($system['isPublic'])) {
            $defaultRestriction = 'public';
          }
        }
        catch (\Throwable $e) {
          // If Tapis lookup fails, keep default as private.
        }
      }

      // Disable the radio if the tenant is in maintenance mode.
      /** @var \Drupal\tapis_tenant\TapisProvider\TapisSiteTenantProviderInterface $tapisSiteTenantProvider */
      $tapisSiteTenantProvider = \Drupal::service('tapis_tenant.tapis_site_tenant_provider');
      // Determine tenant for maintenance check: use entity value if set, else none.
      $tenantId = NULL;
      if ($node->hasField(DrupalIds::SYSTEM_TENANT) && $node->get(DrupalIds::SYSTEM_TENANT)->first()) {
        $tenantId = $node->get(DrupalIds::SYSTEM_TENANT)->first()->getValue()['target_id'];
      }
      $inMaintenance = $tenantId ? $tapisSiteTenantProvider->isTenantInMaintenanceMode($tenantId) : FALSE;

      // Build description, include a note on create.
      $description = t('Private System is only available to their owner<br>Public System is available to all site users.');
      if ($node->isNew()) {
        $description = t('Private System is only available to their owner<br>Public System is available to all site users.<br><em>Note: sharing is applied after the system is published.</em>');
      }

      // Prefer storing the restriction in a field: 'tapis_sys_restrictions'.
      // If the field exists on the form, let core render it; else fallback to
      // legacy radios for backward compatibility.
      if (isset($form['tapis_sys_restrictions'])) {
        // Ensure help text if needed.
        $form['tapis_sys_restrictions']['#description'] = $description;
        // Ensure submit handler runs even when using the field widget.
        if (isset($form['actions']['submit'])) {
          $form['actions']['submit']['#submit'][] = 'tapis_system_form_node_form_share_submit';
        }
      }
      else {
        $form['tapis_system_restrictions'] = [
          '#type' => 'radios',
          '#title' => t('System restrictions'),
          '#required' => TRUE,
          '#options' => [
            'private' => t('Private'),
            'public' => t('Public'),
          ],
          '#default_value' => $defaultRestriction,
          '#description' => $description,
          '#weight' => -4,
          '#disabled' => $inMaintenance,
        ];
        if (isset($form['actions']['submit'])) {
          $form['actions']['submit']['#submit'][] = 'tapis_system_form_node_form_share_submit';
        }
      }
    }

    if (!$node->isNew()) {
      // if the node is NOT new, disable the following fields: tapis id, system type, owner, bucket name, root dir, isDtn, canExec
      $form[DrupalIds::SYSTEM_TAPIS_ID]['widget'][0]['value']['#disabled'] = TRUE;
      $form[DrupalIds::SYSTEM_TAPIS_ID]['widget'][0]['value']['#attributes']['readonly'] = 'readonly';

      $form[DrupalIds::SYSTEM_TYPE]['widget']['#disabled'] = TRUE;
      $form[DrupalIds::SYSTEM_TYPE]['widget']['#attributes']['readonly'] = 'readonly';

      $form[DrupalIds::SYSTEM_OWNER]['widget'][0]['target_id']['#disabled'] = TRUE;
      $form[DrupalIds::SYSTEM_OWNER]['widget'][0]['target_id']['#attributes']['readonly'] = 'readonly';

      $form[DrupalIds::SYSTEM_BUCKET_NAME]['widget'][0]['value']['#disabled'] = TRUE;
      $form[DrupalIds::SYSTEM_BUCKET_NAME]['widget'][0]['value']['#attributes']['readonly'] = 'readonly';

      $form[DrupalIds::SYSTEM_ROOT_DIRECTORY]['widget'][0]['value']['#disabled'] = TRUE;
      $form[DrupalIds::SYSTEM_ROOT_DIRECTORY]['widget'][0]['value']['#attributes']['readonly'] = 'readonly';

      $form[DrupalIds::SYSTEM_IS_DTN]['widget']['value']['#disabled'] = TRUE;
      $form[DrupalIds::SYSTEM_IS_DTN]['widget']['value']['#attributes']['readonly'] = 'readonly';

      $form[DrupalIds::SYSTEM_CAN_EXECUTE_JOBS]['widget']['value']['#disabled'] = TRUE;
      $form[DrupalIds::SYSTEM_CAN_EXECUTE_JOBS]['widget']['value']['#attributes']['readonly'] = 'readonly';
    }
    $form['#validate'][] = 'tapis_system_form_node_form_validate';
  } elseif ($node->bundle() === DrupalIds::NODE_BUNDLE_SYSTEM_SCHEDULER_PROFILE) {
    if (!$node->isNew()) {
      $form['#validate'][] = 'tapis_system_scheduler_profile_form_node_form_validate';
    }
  }
}

/**
 * Run validation for the system node forms.
 * 
 * @param array $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 */
function tapis_system_form_node_form_validate(&$form, &$form_state)
{
  /** @var NodeForm $nodeObject */
  $nodeObject = $form_state->getFormObject();
  /** @var NodeInterface $node */
  $node = $nodeObject->getEntity();

  if ($node->bundle() !== DrupalIds::NODE_BUNDLE_SYSTEM) {
    return;
  }
  // Get the tenant id.
  // $tenantId = $form_state->getValue(DrupalIds::SYSTEM_TENANT)[0]['target_id'];
  if (isset($form_state->getValue(DrupalIds::SYSTEM_TENANT)[0]['target_id'])) {
    $tenantId = $form_state->getValue(DrupalIds::SYSTEM_TENANT)[0]['target_id'];
  } else {
    $tenantId = NULL;
  }

  if ($node->isNew()) {
    // This is a NEW node.

    // Validate tapis id to ensure that it is unique.
    $tapisId = $form_state->getValue(DrupalIds::SYSTEM_TAPIS_ID)[0]['value'];

    if (!$tenantId || !$tapisId) {
      return;
    }

    /** @var Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider */
    $tapisSystemProvider = \Drupal::service("tapis_system.tapis_system_provider");

    if ($tapisSystemProvider->doesSystemIdExist($tenantId, $tapisId)) {
      $form_state->setErrorByName(DrupalIds::SYSTEM_TAPIS_ID, t("A system with this Tapis ID already exists in this tenant. Please enter a different value."));
    }
  }
  else {
    $systemOwner = $node->getOwnerId();
    // Get the tapis id
    $tapisId = $form_state->getValue(DrupalIds::SYSTEM_TAPIS_ID)[0]['value'];
    $tenantId = $form_state->getValue(DrupalIds::SYSTEM_TENANT)[0]['target_id'];

    /* @var \Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider */
    $tapisSystemProvider = \Drupal::service("tapis_system.tapis_system_provider");
    // Get the system.
    $system = $tapisSystemProvider->getSystem($tenantId, $tapisId, $systemOwner);
    $isPublic = $system['isPublic'];
    $sharedWithUsers = $system['sharedWithUsers'];
    $currentUser = \Drupal::currentUser();
    $userPermissionForSystemAdmin = $currentUser->hasPermission('administer tapis system');
    $userPermissionForEditAnyContent = $currentUser->hasPermission('edit any tapis_system content');

    /* @var \Drupal\tapis_auth\TapisProvider\TapisTokenProviderInterface $tapisAuthProvider */
    $tapisAuthProvider = \Drupal::service("tapis_auth.tapis_token_provider");
    $tapisUserId = $tapisAuthProvider->getTapisUsername($tenantId, $currentUser->id());
    if (!$userPermissionForSystemAdmin && !$userPermissionForEditAnyContent &&
      ($currentUser->id() !== $systemOwner) && !$isPublic &&
      !in_array($tapisUserId, $sharedWithUsers)) {
      $form_state->setError($form, "Permission denied.");
    }
  }

  // check if the system uses a static system user
  $use_static_system_user = boolval($form_state->getValue(DrupalIds::SYSTEM_USE_STATIC_SYSTEM_USER)['value']);
  if ($use_static_system_user) {
    // In this case, we need to make sure that the job working directory is set and ENDS WITH "${JobOwner}" or "${JobOwner}/"
    // ${JobOwner} is our own custom "macro" that, at job runtime, will be replaced with the current user's tapis username (ie., the user that's launching the job).
    // we can't use tapis's built-in ${EffectiveUserId} macro because that refers to the system's shared effective user, not the user that's launching the job.
    $jobWorkingDirectory = $form_state->getValue(DrupalIds::SYSTEM_JOB_WORKING_DIR)[0]['value'];
    if (!preg_match('/\${JobOwner}\/?$/', $jobWorkingDirectory)) {
      $form_state->setErrorByName(DrupalIds::SYSTEM_JOB_WORKING_DIR, t('The job working directory must end with "${JobOwner}" or "${JobOwner}/"'));
    }
  }
}

/**
 * Run validation for the system scheduler profile node forms.
 *
 * @param array $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 */
function tapis_system_scheduler_profile_form_node_form_validate(&$form, &$form_state) {
  /** @var NodeForm $nodeObject */
  $nodeObject = $form_state->getFormObject();
  /** @var NodeInterface $node */
  $node = $nodeObject->getEntity();

  if ($node->bundle() !== DrupalIds::NODE_BUNDLE_SYSTEM_SCHEDULER_PROFILE) {
    return;
  }
  $currentUser = \Drupal::currentUser();
  $schpOwner = $node->getOwnerId();
  $userPermissionForSystemAdmin = $currentUser->hasPermission('administer tapis system');
  $userPermissionForEditAnyContent = $currentUser->hasPermission('edit any tapis_system content');
  if (!$userPermissionForSystemAdmin && !$userPermissionForEditAnyContent &&
    $currentUser->id() !== $schpOwner) {
    $form_state->setError($form, "Permission denied.");
  }
}

/**
 * Submit handler to apply tenant sharing based on System restrictions.
 */
function tapis_system_form_node_form_share_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  /** @var NodeForm $nodeObject */
  $nodeObject = $form_state->getFormObject();
  /** @var NodeInterface $node */
  $node = $nodeObject->getEntity();
  if (!$node || $node->bundle() !== DrupalIds::NODE_BUNDLE_SYSTEM) {
    return;
  }

  // Only run when the radio is present.
  // Allow value from either the field or the fallback radios.
  $selected = NULL;
  $field_val = $form_state->getValue('tapis_sys_restrictions');
  if (is_array($field_val)) {
    // Field widget structure like [['value' => 'public']].
    $selected = $field_val[0]['value'] ?? ($field_val['value'] ?? NULL);
  }
  if ($selected === NULL) {
    $selected = $form_state->getValue('tapis_system_restrictions');
  }
  if ($selected === NULL) {
    return;
  }

  // Ensure permissions match what we showed on the form.
  $currentUser = \Drupal::currentUser();
  $userPermissionForSystemAdmin = $currentUser->hasPermission('administer tapis system');
  $userPermissionForEditAnyContent = $currentUser->hasPermission('edit any tapis_system content');
  if (!($userPermissionForSystemAdmin || $userPermissionForEditAnyContent || ($currentUser->id() === $node->getOwnerId()))) {
    return;
  }

  /** @var \Drupal\tapis_tenant\TapisProvider\TapisSiteTenantProviderInterface $tapisSiteTenantProvider */
  $tapisSiteTenantProvider = \Drupal::service('tapis_tenant.tapis_site_tenant_provider');
  $tenantId = $node->get(DrupalIds::SYSTEM_TENANT)->first()->getValue()['target_id'];
  if ($tapisSiteTenantProvider->isTenantInMaintenanceMode($tenantId)) {
    \Drupal::messenger()->addError(t('This system cannot be shared/unshared because its site is in maintenance mode.'));
    return;
  }

  /** @var \Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider */
  $tapisSystemProvider = \Drupal::service('tapis_system.tapis_system_provider');
  $tapisId = $node->get(DrupalIds::SYSTEM_TAPIS_ID)->first()->getValue()['value'];
  $systemOwner = $node->getOwnerId();

  try {
    // If unpublished, nothing to do (system not present in Tapis yet).
    if (!$node->isPublished()) {
      return;
    }

    // Get current state and only act if changed.
    $system = $tapisSystemProvider->getSystem($tenantId, $tapisId, $systemOwner);
    $isPublic = !empty($system['isPublic']);
    if ($selected === 'public' && !$isPublic) {
      $tapisSystemProvider->shareSystemWithTenant($tenantId, $tapisId, $systemOwner);
      \Drupal::messenger()->addStatus(t('This system is now Public for the tenant.'));
    }
    elseif ($selected === 'private' && $isPublic) {
      $tapisSystemProvider->unshareSystemWithTenant($tenantId, $tapisId, $systemOwner);
      \Drupal::messenger()->addStatus(t('This system is now Private (owner only).'));
    }
  }
  catch (\Throwable $e) {
    \Drupal::messenger()->addError(t('Failed to update system sharing: @msg', ['@msg' => $e->getMessage()]));
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc