You are here

class SamlController in SAML Authentication 8.2

Same name and namespace in other branches
  1. 8.3 src/Controller/SamlController.php \Drupal\samlauth\Controller\SamlController
  2. 8 src/Controller/SamlController.php \Drupal\samlauth\Controller\SamlController
  3. 4.x src/Controller/SamlController.php \Drupal\samlauth\Controller\SamlController

Returns responses for samlauth module routes.

Hierarchy

Expanded class hierarchy of SamlController

File

src/Controller/SamlController.php, line 24

Namespace

Drupal\samlauth\Controller
View source
class SamlController extends ControllerBase {

  /**
   * The samlauth SAML service.
   *
   * @var \Drupal\samlauth\SamlService
   */
  protected $saml;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * A configuration object containing samlauth settings.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  protected $config;

  /**
   * The PathValidator service.
   *
   * @var \Drupal\Core\Path\PathValidatorInterface
   */
  protected $pathValidator;

  /**
   * The token service.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected $token;

  /**
   * Constructor for Drupal\samlauth\Controller\SamlController.
   *
   * @param \Drupal\samlauth\SamlService $saml
   *   The samlauth SAML service.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
   *   The PathValidator service.
   * @param \Drupal\Core\Utility\Token $token
   *   The token service.
   */
  public function __construct(SamlService $saml, RequestStack $request_stack, ConfigFactoryInterface $config_factory, PathValidatorInterface $path_validator, Token $token) {
    $this->saml = $saml;
    $this->requestStack = $request_stack;
    $this->config = $config_factory
      ->get('samlauth.authentication');
    $this->pathValidator = $path_validator;
    $this->token = $token;
  }

  /**
   * Factory method for dependency injection container.
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *
   * @return static
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('samlauth.saml'), $container
      ->get('request_stack'), $container
      ->get('config.factory'), $container
      ->get('path.validator'), $container
      ->get('token'));
  }

  /**
   * Initiates a SAML2 authentication flow.
   *
   * This should redirect to the Login service on the IDP and then to our ACS.
   * It does not actually log us in (yet).
   */
  public function login() {
    try {
      $url = $this->saml
        ->login($this
        ->getUrlFromDestination());
    } catch (Exception $e) {
      $this
        ->handleException($e, 'initiating SAML login');
      $url = Url::fromRoute('<front>');
    }
    return $this
      ->createRedirectResponse($url);
  }

  /**
   * Initiate a SAML2 logout flow.
   *
   * This should redirect to the SLS service on the IDP and then to our SLS.
   * It does not actually log us out (yet).
   */
  public function logout() {
    try {
      $url = $this->saml
        ->logout($this
        ->getUrlFromDestination());
    } catch (Exception $e) {
      $this
        ->handleException($e, 'initiating SAML logout');
      $url = Url::fromRoute('<front>');
    }
    return $this
      ->createRedirectResponse($url);
  }

  /**
   * Displays service provider metadata XML for iDP autoconfiguration.
   *
   * @return \Symfony\Component\HttpFoundation\Response
   */
  public function metadata() {
    try {
      $metadata = $this->saml
        ->getMetadata();
    } catch (Exception $e) {
      $this
        ->handleException($e, 'processing SAML SP metadata');
      return $this
        ->createRedirectResponse(Url::fromRoute('<front>'));
    }
    return new Response($metadata, 200, [
      'Content-Type' => 'text/xml',
    ]);
  }

  /**
   * Attribute Consumer Service.
   *
   * This is usually the second step in the authentication flow; the Login
   * service on the IDP should redirect (or: execute a POST request to) here.
   *
   * @return \Drupal\Core\Routing\TrustedRedirectResponse
   */
  public function acs() {
    try {
      $this->saml
        ->acs();
      $url = $this
        ->getRedirectUrlAfterProcessing(TRUE);
    } catch (Exception $e) {
      $this
        ->handleException($e, 'processing SAML authentication response');
      $url = Url::fromRoute('<front>');
    }
    return $this
      ->createRedirectResponse($url);
  }

  /**
   * Single Logout Service.
   *
   * This is usually the second step in the logout flow; the SLS service on the
   * IDP should redirect here.
   *
   * @return \Drupal\Core\Routing\TrustedRedirectResponse
   */
  public function sls() {
    try {
      $url = $this->saml
        ->sls();
      if (!$url) {
        $url = $this
          ->getRedirectUrlAfterProcessing();
      }
    } catch (Exception $e) {
      $this
        ->handleException($e, 'processing SAML single-logout response');
      $url = Url::fromRoute('<front>');
    }
    return $this
      ->createRedirectResponse($url);
  }

  /**
   * Change password redirector.
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   */
  public function changepw() {
    $url = $this->config
      ->get('idp_change_password_service');
    return $this
      ->createRedirectResponse($url);
  }

  /**
   * Constructs a full URL from the 'destination' parameter.
   *
   * @return string|null
   *   The full absolute URL (i.e. leading back to ourselves), or NULL if no
   *   destination parameter was given. This value is tuned to what login() /
   *   logout() expect for an input argument.
   *
   * @throws \RuntimeException
   *   If the destination is disallowed.
   */
  protected function getUrlFromDestination() {
    $destination_url = NULL;
    $destination = $this->requestStack
      ->getCurrentRequest()->query
      ->get('destination');
    $this->requestStack
      ->getCurrentRequest()->query
      ->remove('destination');
    if ($destination) {
      if (UrlHelper::isExternal($destination)) {

        // Prevent authenticating and then redirecting somewhere else.
        throw new \RuntimeException("Destination URL query parameter must not be external: {$destination}");
      }

      // The destination parameter is relative by convention but fromUserInput()
      // requires it to start with '/'. (Note '#' and '?' don't make sense here
      // because that would be expanded to the current URL, which is saml/*.)
      if (strpos($destination, '/') !== 0) {
        $destination = "/{$destination}";
      }
      $destination_url = Url::fromUserInput($destination)
        ->setAbsolute()
        ->toString(TRUE)
        ->getGeneratedUrl();
    }
    return $destination_url;
  }

  /**
   * Returns a URL to redirect to.
   *
   * This should be called only after successfully processing an ACS/logout
   * response.
   *
   * @param bool $logged_in
   *   (optional) TRUE if an ACS request was just processed.
   *
   * @return \Drupal\Core\Url
   *   The URL to redirect to.
   */
  protected function getRedirectUrlAfterProcessing($logged_in = FALSE) {
    if (isset($_REQUEST['RelayState'])) {

      // We should be able to trust the RelayState parameter at this point
      // because the response from the IDP was verified. Only validate general
      // syntax.
      if (!UrlHelper::isValid($_REQUEST['RelayState'], TRUE)) {
        $this
          ->getLogger('samlauth')
          ->error('Invalid RelayState parameter found in request: @relaystate', [
          '@relaystate' => $_REQUEST['RelayState'],
        ]);
      }
      elseif (strpos($_REQUEST['RelayState'], OneLogin_Saml2_Utils::getSelfURLhost() . '/saml/') !== 0) {
        $url = $_REQUEST['RelayState'];
      }
    }
    if (empty($url)) {

      // If no url was specified, we check if it was configured.
      $url = $this->config
        ->get($logged_in ? 'login_redirect_url' : 'logout_redirect_url');
    }
    if ($url) {
      $url = $this->token
        ->replace($url);

      // We don't check access here. If a URL was explicitly specified, we
      // prefer returning a 403 over silently redirecting somewhere else.
      $url_object = $this->pathValidator
        ->getUrlIfValidWithoutAccessCheck($url);
      if (empty($url_object)) {
        $type = $logged_in ? 'Login' : 'Logout';
        $this
          ->getLogger('samlauth')
          ->warning("The {$type} Redirect URL is not a valid path; falling back to default.");
      }
    }
    if (empty($url_object)) {

      // If no url was configured, fall back to a hardcoded route.
      $url_object = Url::fromRoute($logged_in ? 'user.page' : '<front>');
    }
    return $url_object;
  }

  /**
   * Converts a URL to a response object that is suitable for this controller.
   *
   * @param string|\Drupal\Core\Url $url
   *   A URL to redirect to, either as a string or a Drupal URL object. Strings
   *   may only be used by callbacks that are configured in routing.yml as not
   *   being cacheable. (Which, in our case, is most callbacks.)
   *
   * @return \Drupal\Core\Routing\TrustedRedirectResponse
   *   A response object representing a redirect.
   */
  protected function createRedirectResponse($url) {
    if (is_object($url)) {

      // If toString() is used without arguments, this influences requirements
      // for passing cacheability metadata into the response object, which can
      // lead to bugs (see #2630808 short description). We pass TRUE to get
      // cacheability metadata passed back in a GeneratedUrl object instead.
      $generated_url = $url
        ->toString(TRUE);
      $url = $generated_url
        ->getGeneratedUrl();
    }

    // Also when having returned from the IDP, we might redirect to an external
    // url (at least in theory), so we always return a TrustedRedirectResponse.
    $response = new TrustedRedirectResponse($url);
    if (isset($generated_url)) {

      // We shouldn't have to add cacheability metadata to our response object
      // when the route is configured to not cache responses in our routing.yml.
      // Do it anyway to prevent future obscure bugs with new routes.
      $response
        ->addCacheableDependency($generated_url);
    }
    return $response;
  }

  /**
   * Displays error message and logs full exception.
   *
   * @param $exception
   *   The exception thrown.
   * @param string $while
   *   A description of when the error was encountered.
   */
  protected function handleException($exception, $while = '') {
    if ($while) {
      $while = " {$while}";
    }

    // We use the same format for logging as Drupal's ExceptionLoggingSubscriber
    // except we also specify where the error was encountered. (The options are
    // limited, so we make this part of the message, not a context parameter.)
    $error = Error::decodeException($exception);
    unset($error['severity_level']);
    $this
      ->getLogger('samlauth')
      ->critical("%type encountered while {$while}: @message in %function (line %line of %file).", $error);

    // Don't expose the error to prevent information leakage; the user probably
    // can't do much with it anyway. But hint that more details are available.
    drupal_set_message("Error {$while}; details have been logged.", 'error');
  }

}

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.
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.
SamlController::$config protected property A configuration object containing samlauth settings.
SamlController::$pathValidator protected property The PathValidator service.
SamlController::$requestStack protected property The request stack.
SamlController::$saml protected property The samlauth SAML service.
SamlController::$token protected property The token service.
SamlController::acs public function Attribute Consumer Service.
SamlController::changepw public function Change password redirector.
SamlController::create public static function Factory method for dependency injection container. Overrides ControllerBase::create
SamlController::createRedirectResponse protected function Converts a URL to a response object that is suitable for this controller.
SamlController::getRedirectUrlAfterProcessing protected function Returns a URL to redirect to.
SamlController::getUrlFromDestination protected function Constructs a full URL from the 'destination' parameter.
SamlController::handleException protected function Displays error message and logs full exception.
SamlController::login public function Initiates a SAML2 authentication flow.
SamlController::logout public function Initiate a SAML2 logout flow.
SamlController::metadata public function Displays service provider metadata XML for iDP autoconfiguration.
SamlController::sls public function Single Logout Service.
SamlController::__construct public function Constructor for Drupal\samlauth\Controller\SamlController.
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.