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()]));
}
}
