You are here

LoginController.php in LDAP Single Sign On 8

Same filename and directory in other branches
  1. 8.4 src/Controller/LoginController.php

File

src/Controller/LoginController.php
View source
<?php

namespace Drupal\ldap_sso\Controller;

use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Session\AccountInterface;
use Drupal\ldap_authentication\Controller\LoginValidator;
use Drupal\ldap_servers\Logger\LdapDetailLog;
use Drupal\ldap_sso\RedirectResponseWithCookie;
use Drupal\user\UserInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;

/**
 * Class LoginController.
 *
 * @package Drupal\ldap_sso\Controller
 */
class LoginController extends ControllerBase {
  protected $request;
  protected $detailLog;
  protected $config;
  protected $logger;
  protected $validator;

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

  /**
   * 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\LoginValidator $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.
   */
  public function __construct(LoggerInterface $logger, ConfigFactory $configFactory, LoginValidator $validator, LdapDetailLog $detailLog, AccountInterface $account) {
    $this->logger = $logger;
    $this->config = $configFactory
      ->get('ldap_sso.settings');
    $this->validator = $validator;
    $this->detailLog = $detailLog;
    $this->account = $account;
  }

  /**
   * {@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'), $container
      ->get('ldap.detail_log'), $container
      ->get('current_user'));
  }

  /**
   * 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.
   */
  public function login(Request $request) {
    $this->detailLog
      ->log('Beginning SSO login.', [], 'ldap_sso');
    $remote_user = FALSE;
    $realm = NULL;
    if (isset($_SERVER[$this->config
      ->get('ssoVariable')])) {
      $remote_user = $_SERVER[$this->config
        ->get('ssoVariable')];
    }
    if ($this->config
      ->get('ssoSplitUserRealm')) {
      list($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', NULL);
      if ($destination == NULL) {
        $finalDestination = Url::fromRoute('<front>');
      }
      else {
        $finalDestination = Url::fromUserInput($destination);
      }
    }
    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.
    $cookies[] = new Cookie('sso_login_running', '', REQUEST_TIME - 3600, base_path());
    return new RedirectResponseWithCookie($finalDestination
      ->toString(), 302, $cookies);
  }

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

  /**
   * Perform the actual logging in of the user.
   *
   * @param string $remote_user
   *   Remote user name.
   * @param string $realm
   *   Realm information.
   */
  private function loginRemoteUser($remote_user, $realm) {
    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($remote_user) {
    $this->detailLog
      ->log('Starting validation for SSO user.', [], 'ldap_sso');
    $authentication_successful = $this->validator
      ->processSsoLogin(Html::escape($remote_user));
    if ($authentication_successful) {
      $this->detailLog
        ->log('Remote user has local uid @uid', [
        '@uid' => $this->validator
          ->getDrupalUser()
          ->id(),
      ], 'ldap_sso');
      return $this->validator
        ->getDrupalUser();
    }
    else {
      $this->detailLog
        ->log('Remote user not valid.', [], 'ldap_sso');
      return FALSE;
    }
  }

  /**
   * Returns the relevant lifetime from configuration.
   *
   * @return int
   *   Expiration in seconds or 0 for session.
   */
  private function getCookieLifeTime() {
    if ($this->config
      ->get('cookieExpire')) {

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

      // A value quickly in the past.
      $cookie_lifetime = REQUEST_TIME - 3600;
    }
    return $cookie_lifetime;
  }

  /**
   * Finalize login with user not set.
   */
  private function loginUserNotSetFinalize() {
    $this->detailLog
      ->log('User not found, SSO aborted.', [], 'ldap_sso');
    setcookie('sso_stop', 'true', $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) {
    $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() {
    $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', 'true', $this
      ->getCookieLifeTime(), base_path(), 0);
    $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($remote_user) {

    // 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($remote_user) {
    $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,
    ];
  }

}

Classes

Namesort descending Description
LoginController Class LoginController.