You are here

protected function SamlService::doLogin in SAML Authentication 4.x

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

Logs a user in, creating / linking an account; synchronizes attributes.

Split off from acs() to... have at least some kind of split.

Parameters

string $unique_id: The unique ID (attribute value) contained in the SAML response.

\Drupal\Core\Session\AccountInterface|null $account: The existing user account derived from the unique ID, if any.

1 call to SamlService::doLogin()
SamlService::acs in src/SamlService.php
Processes a SAML response (Assertion Consumer Service).

File

src/SamlService.php, line 434

Class

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

Namespace

Drupal\samlauth

Code

protected function doLogin($unique_id, AccountInterface $account = NULL) {
  $config = $this->configFactory
    ->get('samlauth.authentication');
  $first_saml_login = FALSE;
  if (!$account) {
    $this->logger
      ->debug('No matching local users found for unique SAML ID @saml_id.', [
      '@saml_id' => $unique_id,
    ]);

    // Try to link an existing user: first through a custom event handler,
    // then by name, then by email.
    if ($config
      ->get('map_users')) {
      $event = new SamlauthUserLinkEvent($this
        ->getAttributes());
      $this->eventDispatcher
        ->dispatch(SamlauthEvents::USER_LINK, $event);
      $account = $event
        ->getLinkedAccount();
      if ($account) {
        $this->logger
          ->info('Existing user @name (@uid) was newly matched to SAML login attributes; linking user and logging in.', [
          '@name' => $account
            ->getAccountName(),
          '@uid' => $account
            ->id(),
        ]);
      }
    }

    // Linking by name / email: we also select accounts if they are blocked
    // (and throw an exception later on) because 1) we don't want the
    // selection to be dependent on the current account's state; 2) name and
    // email are unique and would otherwise lead to another error while
    // trying to create a new account with duplicate values.
    if (!$account) {
      $name = $this
        ->getAttributeByConfig('user_name_attribute');
      if ($name && ($account_search = $this->entityTypeManager
        ->getStorage('user')
        ->loadByProperties([
        'name' => $name,
      ]))) {
        $account = current($account_search);
        if ($config
          ->get('map_users_name')) {
          $this->logger
            ->info('SAML login for name @name (as provided in a SAML attribute) matches existing Drupal account @uid; linking account and logging in.', [
            '@name' => $name,
            '@uid' => $account
              ->id(),
          ]);
        }
        else {

          // We're not configured to link the account by name, but we still
          // looked it up by name so we can give a better error message than
          // the one caused by trying to save a new account with a duplicate
          // name, later.
          $this->logger
            ->warning('Denying login: SAML login for unique ID @saml_id matches existing Drupal account name @name and we are not configured to automatically link accounts.', [
            '@saml_id' => $unique_id,
            '@name' => $account
              ->getAccountName(),
          ]);
          throw new UserVisibleException('A local user account with your login name already exists, and we are disallowed from linking it.');
        }
      }
    }
    if (!$account) {
      $mail = $this
        ->getAttributeByConfig('user_mail_attribute');
      if ($mail && ($account_search = $this->entityTypeManager
        ->getStorage('user')
        ->loadByProperties([
        'mail' => $mail,
      ]))) {
        $account = current($account_search);
        if ($config
          ->get('map_users_mail')) {
          $this->logger
            ->info('SAML login for email @mail (as provided in a SAML attribute) matches existing Drupal account @uid; linking account and logging in.', [
            '@mail' => $mail,
            '@uid' => $account
              ->id(),
          ]);
        }
        else {

          // Treat duplicate email same as duplicate name above.
          $this->logger
            ->warning('Denying login: SAML login for unique ID @saml_id matches existing Drupal account email @mail and we are not configured to automatically link the account.', [
            '@saml_id' => $unique_id,
            '@mail' => $account
              ->getEmail(),
          ]);
          throw new UserVisibleException('A local user account with your login email address name already exists, and we are disallowed from linking it.');
        }
      }
    }
    if ($account) {
      $this
        ->linkExistingAccount($unique_id, $account);
      $first_saml_login = TRUE;
    }
  }

  // If we haven't found an account to link, create one from the SAML
  // attributes.
  if (!$account) {
    if ($config
      ->get('create_users')) {

      // The register() call will save the account. We want to:
      // - add values from the SAML response into the user account;
      // - not save the account twice (because if the second save fails we do
      //   not want to end up with a user account in an undetermined state);
      // - reuse code (i.e. call synchronizeUserAttributes() with its current
      //   signature, which is also done when an existing user logs in).
      // Because of the third point, we are not passing the necessary SAML
      // attributes into register()'s $account_data parameter, but we want to
      // hook into the save operation of the user account object that is
      // created by register(). It seems we can only do this by implementing
      // hook_user_presave() - which calls our synchronizeUserAttributes().
      $account_data = [
        'name' => $this
          ->getAttributeByConfig('user_name_attribute'),
      ];
      $account = $this->externalAuth
        ->register($unique_id, 'samlauth', $account_data);
      $this->externalAuth
        ->userLoginFinalize($account, $unique_id, 'samlauth');
    }
    else {
      throw new UserVisibleException('No existing user account matches the SAML ID provided. This authentication service is not configured to create new accounts.');
    }
  }
  elseif ($account
    ->isBlocked()) {
    throw new UserVisibleException('Requested account is blocked.');
  }
  else {

    // Synchronize the user account with SAML attributes if needed.
    $this
      ->synchronizeUserAttributes($account, FALSE, $first_saml_login);
    $this->externalAuth
      ->userLoginFinalize($account, $unique_id, 'samlauth');
  }
}