You are here

class LoginController in LDAP Single Sign On 8.4

Same name and namespace in other branches
  1. 8 src/Controller/LoginController.php \Drupal\ldap_sso\Controller\LoginController

Login controller.

@package Drupal\ldap_sso\Controller

Hierarchy

Expanded class hierarchy of LoginController

File

src/Controller/LoginController.php, line 30

Namespace

Drupal\ldap_sso\Controller
View source
class LoginController extends ControllerBase {

  /**
   * Detail log.
   *
   * @var \Drupal\ldap_servers\Logger\LdapDetailLog
   */
  protected $detailLog;

  /**
   * Config.
   *
   * @var \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
   */
  protected $config;

  /**
   * Logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * Login Validator.
   *
   * @var \Drupal\ldap_authentication\Controller\LoginValidatorSso
   */
  protected $validator;

  /**
   * The current user account.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $account;

  /**
   * Time.
   *
   * @var \Drupal\Component\Datetime\Time
   */
  protected $time;

  /**
   * Lookup service.
   *
   * @var \Drupal\ldap_sso\ServerVariableLookup
   */
  protected $serverVariableLookup;

  /**
   * Constructor containing logger and watchdog level.
   *
   * @param \Psr\Log\LoggerInterface $logger
   *   The logging interface.
   * @param \Drupal\Core\Config\ConfigFactory $configFactory
   *   Factory for configuration for LDAP and logging level.
   * @param \Drupal\ldap_authentication\Controller\LoginValidatorSso $validator
   *   Controller for doing the login procedures.
   * @param \Drupal\ldap_servers\Logger\LdapDetailLog $detailLog
   *   Logger interface for conditional logging.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The current user account.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   Time.
   * @param \Drupal\ldap_sso\ServerVariableLookupInterface $server_variable_lookup
   *   Variable used on the server for identified user, often REMOTE_USER.
   */
  public function __construct(LoggerInterface $logger, ConfigFactory $configFactory, LoginValidatorSso $validator, LdapDetailLog $detailLog, AccountInterface $account, TimeInterface $time, ServerVariableLookupInterface $server_variable_lookup) {
    $this->logger = $logger;
    $this->config = $configFactory
      ->get('ldap_sso.settings');
    $this->validator = $validator;
    $this->detailLog = $detailLog;
    $this->account = $account;
    $this->time = $time;
    $this->serverVariableLookup = $server_variable_lookup;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('logger.channel.ldap_sso'), $container
      ->get('config.factory'), $container
      ->get('ldap_authentication.login_validator_sso'), $container
      ->get('ldap.detail_log'), $container
      ->get('current_user'), $container
      ->get('datetime.time'), $container
      ->get('ldap_sso.server_variable'));
  }

  /**
   * Login.
   *
   * A proxy function for the actual authentication routine. This is in place
   * so various implementations of grabbing NTLM credentials can be used and
   * selected from an administration page. This is the real gatekeeper since
   * this assumes that any NTLM authentication from the underlying web server
   * is good enough, and only checks that there are values in place for the
   * user name, and anything else that is set for a particular implementation.
   * In the case that there are no credentials set by the underlying web server,
   * the user is redirected to the normal user login form.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current Symfony HTTP Request.
   *
   *   Redirect response.
   */
  public function login(Request $request) : RedirectResponseWithCookie {
    $this->detailLog
      ->log('Beginning SSO login.', [], 'ldap_sso');
    $remote_user = $this->serverVariableLookup
      ->getAuthenticationNameFromServer($this->config
      ->get('ssoVariable'));
    $realm = NULL;
    if ($remote_user && $this->config
      ->get('ssoSplitUserRealm')) {
      [
        $remote_user,
        $realm,
      ] = $this
        ->splitUserNameRealm($remote_user);
    }
    $this->detailLog
      ->log('SSO raw result is username=@remote_user, (realm=@realm).', [
      '@remote_user' => $remote_user,
      '@realm' => $realm,
    ], 'ldap_sso');
    if ($remote_user) {
      $this->detailLog
        ->log('User found, logging in.', [], 'ldap_sso');
      $this
        ->loginRemoteUser($remote_user, $realm);
      $destination = $request->query
        ->get('destination');

      // In subdirectories we need to remove the base path.
      if ($request
        ->getBasePath()) {
        $base = str_replace('/', '\\/', $request
          ->getBasePath());
        $destination = preg_replace("/^{$base}/", "", $destination);
      }
      $finalDestination = $destination ? Url::fromUserInput($destination) : Url::fromRoute('<front>');
    }
    else {
      $this->detailLog
        ->log('User missing.', [], 'ldap_sso');
      $this
        ->remoteUserMissing();
      $finalDestination = Url::fromRoute('user.login');
    }

    // Removes our automated SSO semaphore, should it have been set.
    if (method_exists(Cookie::class, 'create')) {
      $cookies[] = Cookie::create('sso_login_running', '', $this->time
        ->getRequestTime() - 3600, base_path());
    }
    else {

      // Compatibility support for Drupal 8.9.
      $cookies[] = new Cookie('sso_login_running', '', $this->time
        ->getRequestTime() - 3600, base_path());
    }
    return new RedirectResponseWithCookie($finalDestination
      ->toString(), 302, $cookies);
  }

  /**
   * Access callback.
   */
  public function access() {
    if ($this->account
      ->isAnonymous()) {
      return AccessResult::allowed();
    }
    return AccessResult::forbidden();
  }

  /**
   * Perform the actual logging in of the user.
   *
   * @param string $remote_user
   *   Remote user name.
   * @param string|null $realm
   *   Realm information.
   */
  private function loginRemoteUser(string $remote_user, ?string $realm) : void {
    if ($this->config
      ->get('ssoRemoteUserStripDomainName')) {
      $remote_user = $this
        ->stripDomainName($remote_user);
    }
    $this->detailLog
      ->log('Continuing SSO login with username=@remote_user, (realm=@realm).', [
      '@remote_user' => $remote_user,
      '@realm' => $realm,
    ], 'ldap_sso');
    $user = $this
      ->validateUser($remote_user);
    if ($user && !$user
      ->isAnonymous()) {
      $this
        ->loginUserSetFinalize($user);
    }
    else {
      $this
        ->loginUserNotSetFinalize();
    }
  }

  /**
   * Validate an unvalidated user.
   *
   * @param string $remote_user
   *   Remote user name.
   *
   * @return \Drupal\user\Entity\User|false
   *   Returns the user if available or FALSE when the authentication is not
   *   successful.
   */
  private function validateUser(string $remote_user) {
    $this->detailLog
      ->log('Starting validation for SSO user.', [], 'ldap_sso');
    $this->validator
      ->setAuthname(Html::escape($remote_user));
    $this->validator
      ->processLogin();
    if ($this->validator
      ->getDrupalUser()) {
      $this->detailLog
        ->log('Remote user has local uid @uid', [
        '@uid' => $this->validator
          ->getDrupalUser()
          ->id(),
      ], 'ldap_sso');
      return $this->validator
        ->getDrupalUser();
    }
    $this->detailLog
      ->log('Remote user is not valid.', [], 'ldap_sso');
    return FALSE;
  }

  /**
   * Returns the relevant lifetime from configuration.
   *
   * @return int
   *   Either 0 for session or 1970-01-01 (i.e. "1").
   */
  private function getCookieLifeTime() : int {
    if ($this->config
      ->get('cookieExpire')) {

      // Length of session.
      $cookie_lifetime = 0;
    }
    else {

      // A value quickly in the past.
      $cookie_lifetime = $this->time
        ->getRequestTime() - 3600;
    }
    return $cookie_lifetime;
  }

  /**
   * Finalize login with user not set.
   */
  private function loginUserNotSetFinalize() : void {
    $this->detailLog
      ->log('User not found, SSO aborted.', [], 'ldap_sso');
    setcookie('sso_stop', 'sso_stop', $this
      ->getCookieLifeTime(), base_path(), '');
    $this
      ->messenger()
      ->addError($this
      ->t('Sorry, your LDAP credentials were not found or the LDAP server is not available. You may log in with other credentials on the %user_login_form.', [
      '%user_login_form' => Link::fromTextAndUrl('login form', Url::fromRoute('user.login'))
        ->toString(),
    ]));
    $this->detailLog
      ->log('User not found or server error, redirecting to front page', [], 'ldap_sso');
  }

  /**
   * Finalize login with user set.
   *
   * @param \Drupal\user\UserInterface $account
   *   Valid user account.
   */
  private function loginUserSetFinalize(UserInterface $account) : void {
    $this->detailLog
      ->log('Success with SSO login', [], 'ldap_sso');
    user_login_finalize($account);
    if ($this->config
      ->get('enableLoginConfirmationMessage')) {
      $this
        ->messenger()
        ->addStatus($this
        ->t('You have been successfully authenticated'));
    }
    $this->detailLog
      ->log('Login successful, redirecting to front page.', [], 'ldap_sso');
  }

  /**
   * Handle missing remote user.
   */
  private function remoteUserMissing() : void {
    $this->logger
      ->debug('$_SERVER[\'@variable\'] not found', [
      '@variable' => $this->config
        ->get('ssoVariable'),
    ]);
    $this->detailLog
      ->log('Authentication failure, redirecting to login', [], 'ldap_sso');
    setcookie('sso_stop', 'sso_stop', $this
      ->getCookieLifeTime(), base_path(), '');
    $this
      ->messenger()
      ->addError($this
      ->t('You were not authenticated by the server. You may log in with your credentials below.'));
  }

  /**
   * Strip the domain name from the remote user.
   *
   * @param string $remote_user
   *   The remote user name.
   *
   * @return string
   *   Returns the user without domain.
   */
  private function stripDomainName(string $remote_user) : string {

    // Might be in the form of <remote_user>@<domain> or <domain>\<remote_user>.
    $domain = NULL;
    $exploded = preg_split('/[\\@\\\\]/', $remote_user);
    if (count($exploded) === 2) {
      if (strpos($remote_user, '@') !== FALSE) {
        $remote_user = $exploded[0];
        $domain = $exploded[1];
      }
      else {
        $domain = $exploded[0];
        $remote_user = $exploded[1];
      }
      $this->detailLog
        ->log('Domain stripped: remote_user=@remote_user, domain=@domain', [
        '@remote_user' => $remote_user,
        '@domain' => $domain,
      ], 'ldap_sso');
    }
    return $remote_user;
  }

  /**
   * Split username from realm.
   *
   * @param string $remote_user
   *   String to split at '@'.
   *
   * @return array
   *   Remote user and realm string separated.
   */
  protected function splitUserNameRealm(string $remote_user) : array {
    $realm = NULL;
    $domainMatch = preg_match('/^([A-Za-z0-9_\\-\\.]+)@([A-Za-z0-9_\\-.]+)$/', $remote_user, $matches);
    if ($remote_user && $domainMatch) {
      $remote_user = $matches[1];

      // This can be used later if realms is ever supported properly.
      $realm = $matches[2];
    }
    return [
      $remote_user,
      $realm,
    ];
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityManager protected property The entity manager.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityManager Deprecated protected function Retrieves the entity manager service.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait::redirect
ControllerBase::state protected function Returns the state storage service.
LinkGeneratorTrait::$linkGenerator protected property The link generator. 1
LinkGeneratorTrait::getLinkGenerator Deprecated protected function Returns the link generator.
LinkGeneratorTrait::l Deprecated protected function Renders a link to a route given a route name and its parameters.
LinkGeneratorTrait::setLinkGenerator Deprecated public function Sets the link generator service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
LoginController::$account protected property The current user account.
LoginController::$config protected property Config.
LoginController::$detailLog protected property Detail log.
LoginController::$logger protected property Logger.
LoginController::$serverVariableLookup protected property Lookup service.
LoginController::$time protected property Time.
LoginController::$validator protected property Login Validator.
LoginController::access public function Access callback.
LoginController::create public static function Instantiates a new instance of this class. Overrides ControllerBase::create
LoginController::getCookieLifeTime private function Returns the relevant lifetime from configuration.
LoginController::login public function Login.
LoginController::loginRemoteUser private function Perform the actual logging in of the user.
LoginController::loginUserNotSetFinalize private function Finalize login with user not set.
LoginController::loginUserSetFinalize private function Finalize login with user set.
LoginController::remoteUserMissing private function Handle missing remote user.
LoginController::splitUserNameRealm protected function Split username from realm.
LoginController::stripDomainName private function Strip the domain name from the remote user.
LoginController::validateUser private function Validate an unvalidated user.
LoginController::__construct public function Constructor containing logger and watchdog level.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
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.
UrlGeneratorTrait::$urlGenerator protected property The url generator.
UrlGeneratorTrait::getUrlGenerator Deprecated protected function Returns the URL generator service.
UrlGeneratorTrait::setUrlGenerator Deprecated public function Sets the URL generator service.
UrlGeneratorTrait::url Deprecated protected function Generates a URL or path for a specific route based on the given parameters.