SU-SWS/stanford_profile_helper

View on GitHub
modules/stanford_intranet/src/Commands/IntranetCommands.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

namespace Drupal\stanford_intranet\Commands;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Password\PasswordGeneratorInterface;
use Drupal\Core\State\StateInterface;
use Drupal\externalauth\AuthmapInterface;
use Drupal\stanford_intranet\StanfordIntranetManagerInterface;
use Drush\Commands\DrushCommands;

/**
 * Stanford Intranet Drush commands.
 */
class IntranetCommands extends DrushCommands {

  /**
   * Entity Type Manager Service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * State service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * External Authentication map service.
   *
   * @var \Drupal\externalauth\AuthmapInterface
   */
  protected $authmap;

  /**
   * Core password generator service.
   *
   * @var \Drupal\Core\Password\PasswordGeneratorInterface
   */
  protected $passwordGenerator;

  /**
   * Intranet manager service.
   *
   * @var \Drupal\stanford_intranet\StanfordIntranetManagerInterface
   */
  protected $intranetManager;

  /**
   * Drush command constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   Entity Type Manager Service.
   * @param \Drupal\Core\State\StateInterface $state
   *   State service.
   * @param \Drupal\externalauth\AuthmapInterface $authmap
   *   External Authentication map service.
   * @param \Drupal\Core\Password\PasswordGeneratorInterface $password_generator
   *   Core password generator service.
   * @param \Drupal\stanford_intranet\StanfordIntranetManagerInterface $intranet_manager
   *   Intranet manager service.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, StateInterface $state, AuthmapInterface $authmap, PasswordGeneratorInterface $password_generator, StanfordIntranetManagerInterface $intranet_manager) {
    $this->entityTypeManager = $entity_type_manager;
    $this->state = $state;
    $this->authmap = $authmap;
    $this->passwordGenerator = $password_generator;
    $this->intranetManager = $intranet_manager;
  }

  /**
   * Move files from public to the private file system.
   *
   * @command stanford-intranet:move-files
   */
  public function moveIntranetFiles() {
    $this->intranetManager->moveIntranetFiles();
  }

  /**
   * Enable and configure the intranet.
   *
   * @command stanford-intranet:setup
   * @option roles
   *   Comma delimited list of new roles to create
   * @option affiliations
   *   Comma delimited list of affiliations to limit login restrictions.
   * @option users
   *   Comma delimited list of SunetIDs to create users from.
   * @option workgroups
   *   Comma delimited list of workgroup to limit login access
   * @option role-mapping
   *   Comma delimited list of role mappings in the form `workgroup=role_name`
   * @usage stanford-intranet:setup --roles='Site Group' --workgroups=foo:bar --role-mapping='Foo Bar=site_manager'
   */
  public function setupIntranet($options = [
    'roles' => '',
    'affiliations' => '',
    'users' => '',
    'workgroups' => '',
    'role-mapping' => '',
  ]) {
    // Create the users and roles first in case we need to reference the roles
    // later.
    if (!empty($options['users'])) {
      $this->createUsers(explode(',', $options['users']));
    }
    if (!empty($options['roles'])) {
      $this->createRoles(explode(',', $options['roles']));
    }

    // Enable the intranet and clear caches. Clearing the cache tag will
    // invalidate the Varnish layer if necessary. Then clearing all the cache
    // bins to apply config overrides.
    $this->state->set('stanford_intranet', 1);
    Cache::invalidateTags(['config.system.site']);
    foreach (Cache::getBins() as $cache_backend) {
      $cache_backend->deleteAll();
    }
    $this->moveIntranetFiles();

    $config_page_storage = $this->entityTypeManager->getStorage('config_pages');
    /** @var \Drupal\config_pages\ConfigPagesInterface $config_page */
    $config_page = $config_page_storage->load('stanford_saml');
    // If the config page doesn't exist, we'll create it.
    if (!$config_page) {
      $config_page = $config_page_storage->create([
        'type' => 'stanford_saml',
        'context' => 'a:0:{}',
      ]);
    }

    // Restrict login access to specific affiliations.
    if (!empty($options['affiliations'])) {
      $config_page->set('su_simplesaml_affil', array_unique(explode(',', $options['affiliations'])));
    }
    // Restrict login access to specific workgroups..
    if (!empty($options['workgroups'])) {
      $options['workgroups'] = explode(',', $options['workgroups']);
      // Ensure `uit:sws` is the first in the list.
      array_unshift($options['workgroups'], 'uit:sws');
      $config_page->set('su_simplesaml_allowed', array_unique($options['workgroups']));
    }

    // Apply any desired role mapping.
    if (!empty($options['role-mapping'])) {
      $config_page->set('su_simplesaml_roles', $this->getRoleMappingValues(explode(',', $options['role-mapping'])));
    }

    // If there are specific login restrictions, but there are also users that
    // were created, make sure they are added to the allowed list to login.
    if ((!empty($options['affiliations']) || !empty($options['workgroups'])) && !empty($options['users'])) {
      $config_page->set('su_simplesaml_users', array_unique(explode(',', $options['users'])));
    }

    $config_page->save();
  }

  /**
   * Create users and register them with the external authentication.
   *
   * @param string[] $sunets
   *   Array of SunetsIDs.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createUsers(array $sunets) {
    $user_storage = $this->entityTypeManager->getStorage('user');
    foreach ($sunets as $sunet) {
      // If a user already exists, print a message and move along.
      if ($user_storage->loadByProperties(['name' => $sunet])) {
        $this->output()
          ->write(sprintf('User already exists with the name %s.', $sunet), TRUE);
        continue;
      }
      $new_user = $user_storage->create([
        'name' => $sunet,
        'pass' => $this->passwordGenerator->generate(15),
        'mail' => "$sunet@stanford.edu",
        'roles' => ['site_manager'],
        'status' => 1,
      ]);
      $new_user->save();
      $this->authmap->save($new_user, 'simplesamlphp_auth', $sunet);
    }
  }

  /**
   * Create any custom roles for the site.
   *
   * @param string[] $roles
   *   Array of role labels.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createRoles(array $roles) {
    $role_storage = $this->entityTypeManager->getStorage('user_role');
    foreach ($roles as $role_name) {
      $machine_name = preg_replace('/[^a-z0-9_]/', '_', strtolower($role_name));
      if ($role_storage->load("custm_$machine_name")) {
        $this->output()
          ->write(sprintf('Role already exists with the name %s.', $role_name), TRUE);
        continue;
      }
      $role_storage->create([
        'id' => "custm_$machine_name",
        'label' => $role_name,
      ])->save();
    }
  }

  /**
   * Build the pipe delimited string from the given mappings for the field.
   *
   * @param string[] $mappings
   *   Array of workgroup=role_name strings.
   *
   * @return string
   *   Pipe delimited list of role mappings.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getRoleMappingValues(array $mappings): string {
    // Always make sure uit:sws is mapped.
    $values = ['administrator:eduPersonEntitlement,=,uit:sws'];

    foreach ($mappings as $mapping) {
      [$workgroup, $role_name] = explode('=', $mapping);
      // First look up the role by the id, ex: site_manager.
      $role = $this->entityTypeManager->getStorage('user_role')
        ->load($role_name);

      // If no role was found by the ID, let's try to find it using the label.
      // ex: 'Site Manager'.
      if (!$role) {
        $roles = $this->entityTypeManager->getStorage('user_role')
          ->loadByProperties(['label' => $role_name]);
        // There should only be 1 role with a given name, so let's use that one.
        $role = !empty($roles) ? reset($roles) : NULL;
      }

      // No role was found by the ID or label. Print a message and continue to
      // the next role mapping.
      if (!$role) {
        $this->output()
          ->write(sprintf('No role found with the name %s.', $role_name), TRUE);
        continue;
      }
      $values[] = $role->id() . ":eduPersonEntitlement,=,$workgroup";
    }
    // The field widget uses pipe delimited values, same as the
    // simplesamlphp_auth module. So implode the array of role mappings into a
    // string.
    return implode("|", array_unique($values));
  }

}