You are here

public function ServiceController::handle in CAS 2.x

Same name and namespace in other branches
  1. 8 src/Controller/ServiceController.php \Drupal\cas\Controller\ServiceController::handle()

Main point of communication between CAS server and the Drupal site.

The path that this controller/action handle are always set to the "service" url when authenticating with the CAS server, so CAS server communicates back to the Drupal site using this controller action. That's why there's so much going on in here - it needs to process a few different types of requests.

1 string reference to 'ServiceController::handle'
cas.routing.yml in ./cas.routing.yml
cas.routing.yml

File

src/Controller/ServiceController.php, line 170

Class

ServiceController
Controller used when redirect back from CAS authentication.

Namespace

Drupal\cas\Controller

Code

public function handle() {
  $request = $this->requestStack
    ->getCurrentRequest();

  // First, check if this is a single-log-out (SLO) request from the server.
  if ($request->request
    ->has('logoutRequest')) {
    try {
      $this->casLogout
        ->handleSlo($request->request
        ->get('logoutRequest'));
    } catch (CasSloException $e) {
      $this->casHelper
        ->log(LogLevel::ERROR, 'Error when handling single-log-out request: %error', [
        '%error' => $e
          ->getMessage(),
      ]);
    }

    // Always return a 200 response. CAS Server doesn’t care either way what
    // happens here, since it is a fire-and-forget approach taken.
    return Response::create('', 200);
  }

  /* If there is no ticket parameter on the request, the browser either:
   * (a) is returning from a gateway request to the CAS server in which
   *     the user was not already authenticated to CAS, so there is no
   *     service ticket to validate and nothing to do.
   * (b) has hit this URL for some other reason (crawler, curiosity, etc)
   *     and there is nothing to do.
   * In either case, we just want to redirect them away from this controller.
   */
  if (!$request->query
    ->has('ticket')) {
    $this->casHelper
      ->log(LogLevel::DEBUG, "No CAS ticket found in request to service controller; backing out.");
    $this->casHelper
      ->handleReturnToParameter($request);
    return RedirectResponse::create($this->urlGenerator
      ->generate('<front>'));
  }

  // There is a ticket present, meaning CAS server has returned the browser
  // to the Drupal site so we can authenticate the user locally using the
  // ticket.
  $ticket = $request->query
    ->get('ticket');

  // Our CAS service will need to reconstruct the original service URL
  // when validating the ticket. We always know what the base URL for
  // the service URL is (it's this page), but there may be some query params
  // attached as well (like a destination param) that we need to pass in
  // as well. So, detach the ticket param, and pass the rest off.
  $service_params = $request->query
    ->all();
  unset($service_params['ticket']);
  try {
    $cas_validation_info = $this->casValidator
      ->validateTicket($ticket, $service_params);
  } catch (CasValidateException $e) {

    // Validation failed, redirect to homepage and set message.
    $this->casHelper
      ->log(LogLevel::ERROR, 'Error when validating ticket: %error', [
      '%error' => $e
        ->getMessage(),
    ]);
    $message_validation_failure = $this->casHelper
      ->getMessage('error_handling.message_validation_failure');
    if (!empty($message_validation_failure)) {
      $this->messenger
        ->addError($message_validation_failure);
    }
    return $this
      ->createRedirectResponse($request, TRUE);
  }
  $this->casHelper
    ->log(LogLevel::DEBUG, 'Starting login process for CAS user %username', [
    '%username' => $cas_validation_info
      ->getUsername(),
  ]);

  // Dispatch an event that allows modules to alter any of the CAS data before
  // it's used to lookup a Drupal user account via the authmap table.
  $this->casHelper
    ->log(LogLevel::DEBUG, 'Dispatching EVENT_PRE_USER_LOAD.');
  $this->eventDispatcher
    ->dispatch(CasHelper::EVENT_PRE_USER_LOAD, new CasPreUserLoadEvent($cas_validation_info));
  if ($cas_validation_info
    ->getUsername() !== $cas_validation_info
    ->getOriginalUsername()) {
    $this->casHelper
      ->log(LogLevel::DEBUG, 'Username was changed from %original to %new from a subscriber.', [
      '%original' => $cas_validation_info
        ->getOriginalUsername(),
      '%new' => $cas_validation_info
        ->getUsername(),
    ]);
  }

  // At this point, the ticket is validated and third-party modules got the
  // chance to alter the username and also perform other 'pre user load'
  // tasks. Before authenticating the user locally, let's allow third-party
  // code to inject user interaction into the flow.
  // @see \Drupal\cas\Event\CasPreUserLoadRedirectEvent
  $cas_pre_user_load_redirect_event = new CasPreUserLoadRedirectEvent($ticket, $cas_validation_info, $service_params);
  $this->casHelper
    ->log(LogLevel::DEBUG, 'Dispatching EVENT_PRE_USER_LOAD_REDIRECT.');
  $this->eventDispatcher
    ->dispatch(CasHelper::EVENT_PRE_USER_LOAD_REDIRECT, $cas_pre_user_load_redirect_event);

  // A subscriber might have set an HTTP redirect response allowing potential
  // user interaction to be injected into the flow.
  $redirect_response = $cas_pre_user_load_redirect_event
    ->getRedirectResponse();
  if ($redirect_response) {
    $this->casHelper
      ->log(LogLevel::DEBUG, 'Redirecting to @url as requested by one of EVENT_PRE_USER_LOAD event subscribers.', [
      '@url' => $redirect_response
        ->getTargetUrl(),
    ]);
    return $redirect_response;
  }

  // Now that the ticket has been validated, we can use the information from
  // validation request to authenticate the user locally on the Drupal site.
  try {
    $this->casUserManager
      ->login($cas_validation_info, $ticket);
    $login_success_message = $this->casHelper
      ->getMessage('login_success_message');
    if (!empty($login_success_message)) {
      $this->messenger
        ->addStatus($login_success_message);
    }
  } catch (CasLoginException $e) {

    // Use an appropriate log level depending on exception type.
    if (empty($e
      ->getCode()) || $e
      ->getCode() === CasLoginException::ATTRIBUTE_PARSING_ERROR) {
      $error_level = LogLevel::ERROR;
    }
    else {
      $error_level = LogLevel::INFO;
    }
    $this->casHelper
      ->log($error_level, $e
      ->getMessage());

    // Display error message to the user, unless this login failure originated
    // from a gateway login. No sense in showing them an error when the login
    // is optional.
    $login_error_message = $this
      ->getLoginErrorMessage($e);
    if ($login_error_message && !$request->query
      ->has('from_gateway')) {
      $this->messenger
        ->addError($login_error_message, 'error');
    }
    return $this
      ->createRedirectResponse($request, TRUE);
  }
  return $this
    ->createRedirectResponse($request);
}