You are here

public function SamlService::sls in SAML Authentication 4.x

Same name and namespace in other branches
  1. 8.3 src/SamlService.php \Drupal\samlauth\SamlService::sls()
  2. 8.2 src/SamlService.php \Drupal\samlauth\SamlService::sls()

Does processing for the Single Logout Service.

Return value

null|string Usually returns nothing. May return a URL to redirect to.

File

src/SamlService.php, line 653

Class

SamlService
Governs communication between the SAML toolkit and the IdP / login behavior.

Namespace

Drupal\samlauth

Code

public function sls() {
  $config = $this->configFactory
    ->get('samlauth.authentication');

  // We might at some point check if this code can be abstracted a bit...
  if ($config
    ->get('debug_log_in')) {
    if (isset($_GET['SAMLResponse'])) {
      $response = base64_decode($_GET['SAMLResponse']);
      if ($response) {
        $this->logger
          ->debug("SLS received 'SAMLResponse' in GET request (base64 decoded): <pre>@message</pre>", [
          '@message' => $response,
        ]);
      }
      else {
        $this->logger
          ->warning("SLS received 'SAMLResponse' in GET request which could not be base64 decoded: <pre>@message</pre>", [
          '@message' => $_POST['SAMLResponse'],
        ]);
      }
    }
    elseif (isset($_GET['SAMLRequest'])) {
      $response = base64_decode($_GET['SAMLRequest']);
      if ($response) {
        $this->logger
          ->debug("SLS received 'SAMLRequest' in GET request (base64 decoded): <pre>@message</pre>", [
          '@message' => $response,
        ]);
      }
      else {
        $this->logger
          ->warning("SLS received 'SAMLRequest' in GET request which could not be base64 decoded: <pre>@message</pre>", [
          '@message' => $_POST['SAMLRequest'],
        ]);
      }
    }
    else {

      // Not sure if we should be more detailed...
      $this->logger
        ->warning("HTTP request to SLS is not a GET request, or contains no 'SAMLResponse'/'SAMLRequest' parameters.");
    }
  }

  // Perform flood control; see acs().
  $flood_config = $this->configFactory
    ->get('user.flood');
  if (!$this->flood
    ->isAllowed('samlauth.failed_logout_ip', $flood_config
    ->get('ip_limit'), $flood_config
    ->get('ip_window'))) {
    throw new TooManyRequestsHttpException(NULL, 'Access is blocked because of IP based flood prevention.');
  }
  try {

    // This line means we're extracting logic previously encapsulated inside
    // the Auth class. That's slightly unfortunate but doesn't compare to all
    // other considerations still needing to be made re. refactoring logic.
    $purpose = isset($_GET['SAMLResponse']) ? 'sls-response' : 'sls-request';

    // Unlike the 'logout()' route, we only log the user out if we have a
    // valid request/response, so first have the SAML Toolkit check things.
    // Don't have it do any session actions, because nothing is needed
    // besides our own logout actions (if any). This call can either set an
    // error condition or throw a \OneLogin_Saml2_Error, depending on whether
    // we are processing a POST request; don't catch anything.
    // @todo should we check a LogoutResponse against the ID of the
    //   LogoutRequest we sent earlier? Seems to be not absolutely required on
    //   top of the validity / signature checks which the library already does
    //   - but every extra check is good. Maybe make it optional.
    $url = $this
      ->getSamlAuth($purpose)
      ->processSLO(TRUE, NULL, (bool) $config
      ->get('security_logout_reuse_sigs'), NULL, TRUE);
  } catch (\Exception $e) {
    $this->flood
      ->register('samlauth.failed_logout_ip', $flood_config
      ->get('ip_window'));
    throw $e;
  }
  if ($config
    ->get('debug_log_saml_in')) {

    // There should be no way we can get here if neither GET parameter is set;
    // if nothing gets logged, that's a bug.
    if (isset($_GET['SAMLResponse'])) {
      $this->logger
        ->debug('SLS received SAML response: <pre>@message</pre>', [
        '@message' => $this
          ->getSamlAuth($purpose)
          ->getLastResponseXML(),
      ]);
    }
    elseif (isset($_GET['SAMLRequest'])) {
      $this->logger
        ->debug('SLS received SAML request: <pre>@message</pre>', [
        '@message' => $this
          ->getSamlAuth($purpose)
          ->getLastRequestXML(),
      ]);
    }
  }

  // Now look if there were any errors and also throw.
  $errors = $this
    ->getSamlAuth($purpose)
    ->getErrors();
  if (!empty($errors)) {

    // We have one or multiple error types / short descriptions, and one
    // 'reason' for the last error.
    throw new \RuntimeException('Error(s) encountered during processing of SLS response. Type(s): ' . implode(', ', array_unique($errors)) . '; reason given for last error: ' . $this
      ->getSamlAuth($purpose)
      ->getLastErrorReason());
  }

  // Remove SAML session data, log the user out of Drupal, and return a
  // redirect URL if we got any. Usually,
  // - a LogoutRequest means we need to log out and redirect back to the IdP,
  //   for which the SAML Toolkit returned a URL.
  // - after a LogoutResponse we don't need to log out because we already did
  //   that at the start of the process, in logout() - but there's nothing
  //   against checking. We did not get an URL returned and our caller can
  //   decide what to do next.
  $this
    ->drupalLogoutHelper();
  return $url;
}