You are here

class LDAPAuthorizationProvider in Lightweight Directory Access Protocol (LDAP) 8.4

Same name and namespace in other branches
  1. 8.3 ldap_authorization/src/Plugin/authorization/Provider/LDAPAuthorizationProvider.php \Drupal\ldap_authorization\Plugin\authorization\Provider\LDAPAuthorizationProvider

The LDAP authorization provider for authorization module.

Plugin annotation


@AuthorizationProvider(
  id = "ldap_provider",
  label = @Translation("LDAP Authorization")
)

Hierarchy

Expanded class hierarchy of LDAPAuthorizationProvider

1 file declares its use of LDAPAuthorizationProvider
LdapAuthorizationProviderTest.php in ldap_authorization/tests/src/Unit/LdapAuthorizationProviderTest.php

File

ldap_authorization/src/Plugin/authorization/Provider/LDAPAuthorizationProvider.php, line 26

Namespace

Drupal\ldap_authorization\Plugin\authorization\Provider
View source
class LDAPAuthorizationProvider extends ProviderPluginBase {
  use LdapTransformationTraits;

  /**
   * {@inheritdoc}
   */
  protected $handlers = [
    'ldap',
    'ldap_authentication',
  ];

  /**
   * {@inheritdoc}
   */
  protected $syncOnLogonSupported = TRUE;

  /**
   * {@inheritdoc}
   */
  protected $revocationSupported = TRUE;

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

  /**
   * Drupal User Processor.
   *
   * @var \Drupal\ldap_user\Processor\DrupalUserProcessor
   */
  protected $drupalUserProcessor;

  /**
   * Constructor.
   *
   * @param array $configuration
   *   Configuration.
   * @param string $plugin_id
   *   Plugin ID.
   * @param array $plugin_definition
   *   Plugin definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   Entity type manager.
   * @param \Drupal\ldap_user\Processor\DrupalUserProcessor $drupal_user_processor
   *   Drupal user processor.
   */
  public function __construct(array $configuration, string $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, DrupalUserProcessor $drupal_user_processor) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->entityTypeManager = $entity_type_manager;
    $this->drupalUserProcessor = $drupal_user_processor;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('entity_type.manager'), $container
      ->get('ldap.drupal_user_processor'));
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) : array {

    /** @var \Drupal\authorization\Entity\AuthorizationProfile $profile */
    $profile = $this->configuration['profile'];
    $tokens = $this
      ->getTokens();
    $tokens += $profile
      ->getTokens();
    if ($profile
      ->hasValidConsumer() && method_exists($profile
      ->getConsumer(), 'getTokens')) {
      $tokens += $profile
        ->getConsumer()
        ->getTokens();
    }
    $storage = $this->entityTypeManager
      ->getStorage('ldap_server');
    $query_results = $storage
      ->getQuery()
      ->execute();

    /** @var \Drupal\ldap_servers\Entity\Server[] $servers */
    $servers = $storage
      ->loadMultiple($query_results);
    $form['status'] = [
      '#type' => 'fieldset',
      '#title' => $this
        ->t('Base configuration'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    ];
    if (count($servers) === 0) {
      $form['status']['server'] = [
        '#type' => 'markup',
        '#markup' => $this
          ->t('<strong>Warning</strong>: You must create an LDAP Server first.'),
      ];
      $this
        ->messenger()
        ->addWarning($this
        ->t('You must create an LDAP Server first.'));
    }
    else {
      $server_options = [];
      foreach ($servers as $id => $server) {

        /** @var \Drupal\ldap_servers\Entity\Server $server */
        $server_options[$id] = $server
          ->label() . ' (' . $server
          ->get('address') . ')';
      }
    }
    $provider_config = $profile
      ->getProviderConfig();
    if (!empty($server_options)) {
      if (isset($provider_config['status'])) {
        $default_server = $provider_config['status']['server'];
      }
      elseif (count($server_options) === 1) {
        $default_server = key($server_options);
      }
      else {
        $default_server = '';
      }
      $form['status']['server'] = [
        '#type' => 'radios',
        '#title' => $this
          ->t('LDAP Server used in @profile_name configuration.', $tokens),
        '#required' => TRUE,
        '#default_value' => $default_server,
        '#options' => $server_options,
      ];
    }
    $form['status']['only_ldap_authenticated'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Only apply the following <strong>LDAP</strong> to <strong>@consumer_name</strong> configuration to users authenticated via LDAP', $tokens),
      '#description' => $this
        ->t('One uncommon reason for disabling this is when you are using Drupal authentication, but want to leverage LDAP for authorization; for this to work the Drupal username still has to map to an LDAP entry.'),
      '#default_value' => $provider_config['status']['only_ldap_authenticated'] ?? '',
    ];
    $form['filter_and_mappings'] = [
      '#type' => 'fieldset',
      '#title' => $this
        ->t('LDAP to @consumer_name mapping and filtering', $tokens),
      '#description' => $this
        ->t('Representations of groups derived from LDAP might initially look like:
        <ul>
        <li><code>cn=students,ou=groups,dc=hogwarts,dc=edu</code></li>
        <li><code>cn=gryffindor,ou=groups,dc=hogwarts,dc=edu</code></li>
        <li><code>cn=faculty,ou=groups,dc=hogwarts,dc=edu</code></li>
        </ul>
        <strong>Warning: If you enable "Create <em>@consumer_name</em> targets if they do not exist" under conditions, all LDAP groups will be synced!</strong>', $tokens),
      '#collapsible' => TRUE,
    ];
    $form['filter_and_mappings']['use_first_attr_as_groupid'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Convert full DN to value of first attribute before mapping'),
      '#description' => $this
        ->t('Example: <code>cn=students,ou=groups,dc=hogwarts,dc=edu</code> would be converted to <code>students</code>'),
      '#default_value' => $provider_config['filter_and_mappings']['use_first_attr_as_groupid'] ?? '',
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function buildRowForm(array $form, FormStateInterface $form_state, $index = 0) : array {
    $row = [];

    /** @var \Drupal\authorization\Entity\AuthorizationProfile $profile */
    $profile = $this->configuration['profile'];
    $mappings = $profile
      ->getProviderMappings();
    $row['query'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('LDAP query'),
      '#maxlength' => 254,
      '#default_value' => $mappings[$index]['query'] ?? NULL,
    ];
    $row['is_regex'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Is this query a regular expression?'),
      '#description' => $this
        ->t('Example (note the "i" for case-insensitive): %example', [
        '%example' => new FormattableMarkup('<code>/^memberOf=staff/i</code>', []),
      ]),
      '#default_value' => $mappings[$index]['is_regex'] ?? NULL,
    ];
    return $row;
  }

  /**
   * {@inheritdoc}
   */
  public function getProposals(UserInterface $user) : array {

    // Do not continue if user should be excluded from LDAP authentication.
    if ($this->drupalUserProcessor
      ->excludeUser($user)) {
      throw new AuthorizationSkipAuthorization('User in list of excluded users');
    }

    /** @var \Drupal\authorization\Entity\AuthorizationProfile $profile */
    $profile = $this->configuration['profile'];
    $config = $profile
      ->getProviderConfig();

    // Load the correct server.
    $server_id = $config['status']['server'];

    /** @var \Drupal\ldap_servers\Entity\Server $server */
    $server = \Drupal::service('entity_type.manager')
      ->getStorage('ldap_server')
      ->load($server_id);
    if (!$server
      ->status()) {
      return [];
    }

    /** @var \Drupal\ldap_servers\LdapUserManager $ldap_user_manager */
    $ldap_user_manager = \Drupal::service('ldap.user_manager');
    $ldap_user_manager
      ->setServer($server);
    $ldap_user_data = $ldap_user_manager
      ->getUserDataByAccount($user);
    if (!$ldap_user_data && $user
      ->isNew()) {

      // If we don't have a real user yet, fall back to the account name.
      $ldap_user_data = $ldap_user_manager
        ->getUserDataByIdentifier($user
        ->getAccountName());
    }
    if (!$ldap_user_data && $this->configuration['status']['only_ldap_authenticated'] === TRUE) {
      throw new AuthorizationSkipAuthorization('Not LDAP authenticated');
    }

    /** @var \Drupal\ldap_servers\LdapGroupManager $group_manager */
    $group_manager = \Drupal::service('ldap.group_manager');
    $group_manager
      ->setServerById($server_id);

    // Get user groups from DN.
    $derive_from_dn_authorizations = $group_manager
      ->groupUserMembershipsFromDn($user
      ->getAccountName());
    if (!$derive_from_dn_authorizations) {
      $derive_from_dn_authorizations = [];
    }

    // Get user groups from membership.
    $group_dns = $group_manager
      ->groupMembershipsFromUser($user
      ->getAccountName());
    if (!$group_dns) {
      $group_dns = [];
    }
    $proposed_ldap_authorizations = array_merge($derive_from_dn_authorizations, $group_dns);
    $proposed_ldap_authorizations = array_unique($proposed_ldap_authorizations);
    \Drupal::service('ldap.detail_log')
      ->log('Available authorizations to test: @authorizations', [
      '@authorizations' => implode("\n", $proposed_ldap_authorizations),
    ], 'ldap_authorization');
    if (count($proposed_ldap_authorizations)) {
      return array_combine($proposed_ldap_authorizations, $proposed_ldap_authorizations);
    }
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function filterProposals(array $proposedLdapAuthorizations, array $providerMapping) : array {
    $filtered_proposals = [];
    foreach ($proposedLdapAuthorizations as $key => $value) {
      if ($providerMapping['is_regex']) {
        $pattern = $providerMapping['query'];
        try {
          if (preg_match($pattern, $value, $matches)) {

            // If there is a sub-pattern then return the first one.
            // Named sub-patterns not supported.
            if (count($matches) > 1) {
              $filtered_proposals[$key] = $matches[1];
            }
            elseif (count($matches) === 1) {
              $filtered_proposals[$key] = $matches[0];
            }
            else {
              $filtered_proposals[$key] = $value;
            }
          }
        } catch (\Exception $e) {
          \Drupal::logger('ldap')
            ->error('Error in matching regular expression @regex', [
            '@regex' => $pattern,
          ]);
        }
      }
      elseif (mb_strtolower($value) === mb_strtolower($providerMapping['query'])) {
        $filtered_proposals[$key] = $value;
      }
    }
    return $filtered_proposals;
  }

  /**
   * {@inheritdoc}
   */
  public function sanitizeProposals(array $proposals) : array {

    // Configure this provider.

    /** @var \Drupal\authorization\Entity\AuthorizationProfile $profile */
    $profile = $this->configuration['profile'];
    $config = $profile
      ->getProviderConfig();
    foreach ($proposals as $key => $authorization_id) {

      /** @var string $lowercase_key */
      $lowercase_key = \mb_strtolower($key);
      if ($config['filter_and_mappings']['use_first_attr_as_groupid']) {
        $attr_parts = self::splitDnWithAttributes($authorization_id);
        unset($attr_parts['count']);
        if (count($attr_parts) > 0) {
          $first_part = \explode('=', $attr_parts[0]);
          if ($first_part && isset($first_part[1])) {
            $authorization_id = ConversionHelper::unescapeDnValue(trim($first_part[1]));
          }
        }
        $lowercase_key = \mb_strtolower($authorization_id);
      }
      $proposals[$lowercase_key] = $authorization_id;
      if ($key !== $lowercase_key) {
        unset($proposals[$key]);
      }
    }
    return $proposals;
  }

  /**
   * {@inheritdoc}
   */
  public function validateRowForm(array &$form, FormStateInterface $form_state) : void {
    parent::validateRowForm($form, $form_state);
    foreach ($form_state
      ->getValues() as $value) {
      if (isset($value['provider_mappings']) && $value['provider_mappings']['is_regex'] == 1 && @preg_match($value['provider_mappings']['query'], '') === FALSE) {
        $form_state
          ->setErrorByName('mapping', $this
          ->t('Invalid regular expression'));
      }
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ConfigurableAuthorizationPluginBase::buildRowDescription public function Builds the authorization row description. Overrides ConfigurableAuthorizationPluginInterface::buildRowDescription
ConfigurableAuthorizationPluginBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies
ConfigurableAuthorizationPluginBase::defaultConfiguration public function Gets default configuration for this plugin. Overrides ConfigurableInterface::defaultConfiguration
ConfigurableAuthorizationPluginBase::getConfiguration public function Unused, configuration is saved in the profile, required by base class. Overrides ConfigurableInterface::getConfiguration
ConfigurableAuthorizationPluginBase::getDescription public function Returns the plugin's description. Overrides ConfigurableAuthorizationPluginInterface::getDescription
ConfigurableAuthorizationPluginBase::getTokens public function Tokens for the relevant plugin. Overrides ConfigurableAuthorizationPluginInterface::getTokens
ConfigurableAuthorizationPluginBase::getType public function
ConfigurableAuthorizationPluginBase::label public function Returns the label for use on the administration pages. Overrides ConfigurableAuthorizationPluginInterface::label
ConfigurableAuthorizationPluginBase::setConfiguration public function Unused, configuration is saved in the profile, required by base class. Overrides ConfigurableInterface::setConfiguration
ConfigurableAuthorizationPluginBase::submitConfigurationForm public function Form submission handler. Overrides PluginFormInterface::submitConfigurationForm
ConfigurableAuthorizationPluginBase::submitRowForm public function Submits the authorization form row. Overrides ConfigurableAuthorizationPluginInterface::submitRowForm
ConfigurableAuthorizationPluginBase::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
DependencyTrait::$dependencies protected property The object's dependencies.
DependencyTrait::addDependencies protected function Adds multiple dependencies.
DependencyTrait::addDependency protected function Adds a dependency.
LDAPAuthorizationProvider::$drupalUserProcessor protected property Drupal User Processor.
LDAPAuthorizationProvider::$entityTypeManager protected property Entity Type Manager.
LDAPAuthorizationProvider::$handlers protected property List of modules handling this provider. Overrides ProviderPluginBase::$handlers
LDAPAuthorizationProvider::$revocationSupported protected property Whether this provider supports revocation. Overrides ProviderPluginBase::$revocationSupported
LDAPAuthorizationProvider::$syncOnLogonSupported protected property Whether this provider supports sync on user logon. Overrides ProviderPluginBase::$syncOnLogonSupported
LDAPAuthorizationProvider::buildConfigurationForm public function Form constructor. Overrides ConfigurableAuthorizationPluginBase::buildConfigurationForm
LDAPAuthorizationProvider::buildRowForm public function Builds the authorization form row. Overrides ConfigurableAuthorizationPluginBase::buildRowForm
LDAPAuthorizationProvider::create public static function Creates an instance of the plugin. Overrides ConfigurableAuthorizationPluginBase::create
LDAPAuthorizationProvider::filterProposals public function Provider-side filtering. Overrides ProviderInterface::filterProposals
LDAPAuthorizationProvider::getProposals public function Get the proposals for this users. Overrides ProviderInterface::getProposals
LDAPAuthorizationProvider::sanitizeProposals public function Sanitize proposals. Overrides ProviderInterface::sanitizeProposals
LDAPAuthorizationProvider::validateRowForm public function Validates the authorization form row. Overrides ConfigurableAuthorizationPluginBase::validateRowForm
LDAPAuthorizationProvider::__construct public function Constructor. Overrides ConfigurableAuthorizationPluginBase::__construct
LdapTransformationTraits::ldapEscapeDn protected function Wrapper for ldap_escape().
LdapTransformationTraits::ldapEscapeFilter protected function Wrapper for ldap_escape().
LdapTransformationTraits::php56PolyfillLdapEscape public static function Stub implementation of the {@link ldap_escape()} function of ext-ldap.
LdapTransformationTraits::splitDnWithAttributes public static function Wrapper for ldap_explode_dn().
LdapTransformationTraits::splitDnWithValues public static function Wrapper for ldap_explode_dn().
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
ProviderPluginBase::$type protected property Defines the type, for example used by getToken(). Overrides ConfigurableAuthorizationPluginBase::$type
ProviderPluginBase::getHandlers public function Which modules are handling this provider.
ProviderPluginBase::isSyncOnLogonSupported public function Provides sync on logon. Overrides ProviderInterface::isSyncOnLogonSupported
ProviderPluginBase::revocationSupported public function Provides revocation. Overrides ProviderInterface::revocationSupported
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.