You are here

simplesamlphp_auth.module in simpleSAMLphp Authentication 8.3

SimpleSAMLphp authentication module for Drupal.

This authentication module is based on the shibboleth authentication module, with changes to adopt to use simpleSAMLphp.

ISSUES and TODOs: ISSUE: User is always dropped on user page after login, instead of where they were when they clicked "Federated Log In". Because of this, deep linking to access controlled content does not work. Usability would be considerably increased if this were resolved. FYI: Drupal now requires knowledge of the local user password in order to change e-mail address, etc. This could be an issue for users of accounts that are autoprovisioned by this module, though Drupal does give users the ability to reset their password to something they know via the Request new password feature. KLUDGE: Drupal does not kill the session on logout, even with drupal_session_destroy_uid(), so I had to use session_destroy().

@todo Rework the default login limitation logic to use a drupal permission rather than a list of UIDs. @todo When denying access because the administrator has chosen not to allow the module to register/create accounts, the user is told to contact the administrator; the message should provide the contact information. ISSUE: Until Drupal issue #754560 is resolved users will not see logout notices.

File

simplesamlphp_auth.module
View source
<?php

/**
 * @file
 * SimpleSAMLphp authentication module for Drupal.
 *
 * This authentication module is based on the shibboleth authentication module,
 * with changes to adopt to use simpleSAMLphp.
 *
 * ISSUES and TODOs:
 *  ISSUE: User is always dropped on user page after login, instead of where
 *         they were when they clicked "Federated Log In". Because of this, deep
 *         linking to access controlled content does not work. Usability would
 *         be considerably increased if this were resolved.
 *  FYI: Drupal now requires knowledge of the local user password in order to
 *       change e-mail address, etc. This could be an issue for users of
 *       accounts that are autoprovisioned by this module, though Drupal does
 *       give users the ability to reset their password to something they know
 *       via the Request new password feature.
 *  KLUDGE: Drupal does not kill the session on logout, even with
 *          drupal_session_destroy_uid(), so I had to use session_destroy().
 *
 * @todo Rework the default login limitation logic to use a drupal permission
 *        rather than a list of UIDs.
 * @todo When denying access because the administrator has chosen not to allow
 *        the module to register/create accounts, the user is told to contact
 *        the administrator; the message should provide the contact information.
 *  ISSUE: Until Drupal issue #754560 is resolved users will not see logout
 *         notices.
 */
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_help().
 */
function simplesamlphp_auth_help($route_name) {
  switch ($route_name) {
    case 'simplesamlphp_auth.admin_settings':
    case 'help.page.simplesamlphp_auth':
      $output = t('<p>This module integrates Drupal with a SimpleSAMLphp Service Point (SP), effectively federating Drupal.</p>');
      return $output;
  }
}

/**
 * Implements hook_user_logout().
 */
function simplesamlphp_auth_user_logout(AccountInterface $account) {
  $logout_url = \Drupal::config('simplesamlphp_auth.settings')
    ->get('logout_goto_url');

  /** @var \Drupal\simplesamlphp_auth\Service\SimplesamlphpAuthManager $simplesaml */
  $simplesaml = \Drupal::service('simplesamlphp_auth.manager');
  $session = \Drupal::service('session_manager');

  // Only interfere if this user was logged in through simplesaml.
  if ($simplesaml
    ->isActivated() && $simplesaml
    ->isAuthenticated()) {

    // Have to destroy the session here as some configurations of
    // SimpleSAMLphp_auth can create infinite loops. By removing IdP auth before
    // Drupal auth, checks for local authentication will trigger before the
    // session is destroyed naturally. We must therefore destroy the session
    // manually here.
    // Copied from user.module method user_logout().
    $session
      ->destroy();
    $account
      ->setAccount(new AnonymousUserSession());
    if ($logout_url) {
      $simplesaml
        ->logout($logout_url);
    }
    else {
      $simplesaml
        ->logout();
    }
  }
}

/**
 * Implements hook_entity_extra_field_info().
 */
function simplesamlphp_auth_entity_extra_field_info() {
  $fields = [];
  $fields['user']['user']['form']['simplesamlphp_auth_user_enable'] = [
    'label' => t('SAML authentication settings'),
    'description' => '',
    'weight' => 0,
    'visible' => TRUE,
  ];
  return $fields;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters the user register form to include a checkbox signifying the user
 * should be SimpleSAML enabled. Removes password fields if the IdP
 * is the sole place for password management.
 *
 * @see AccountForm::form()
 * @see simplesamlphp_auth_user_form_submit()
 */
function simplesamlphp_auth_form_user_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  simplesamlphp_auth_user_form_includes($form);
  $authmap = \Drupal::service('externalauth.authmap');

  // If the user has a simplesamlphp_auth authmap record, then don't require
  // them to know their Drupal password. This will allow them to change their
  // e-mail address, and set a Drupal password if they want to
  // (and are allowed).
  $account = $form_state
    ->getFormObject()
    ->getEntity();
  $saml_enabled = $authmap
    ->get($account
    ->id(), 'simplesamlphp_auth');
  if ($saml_enabled) {
    $form['simplesamlphp_auth_user_enable']['#default_value'] = TRUE;

    // If the user is a simplesamlphp_auth user and is NOT allowed to set their
    // Drupal password, remove the fields from the form.
    $config = \Drupal::config('simplesamlphp_auth.settings');
    if (!$config
      ->get('allow.set_drupal_pwd')) {
      $form['account']['current_pass']['#access'] = FALSE;
      $form['account']['pass']['#access'] = FALSE;
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters the user register form to include a checkbox signifying the user
 * should be SimpleSAML enabled.
 *
 * @see AccountForm::form()
 * @see simplesamlphp_auth_user_form_submit()
 */
function simplesamlphp_auth_form_user_register_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  simplesamlphp_auth_user_form_includes($form);
  $form['simplesamlphp_auth_user_enable']['#default_value'] = TRUE;
}

/**
 * Helper function to include the SimpleSAML checkbox on user forms.
 *
 * @param array $form
 *   The user account form.
 *
 * @see simplesamlphp_auth_form_user_form_alter()
 * @see simplesamlphp_auth_form_user_register_form_alter()
 * @see simplesamlphp_auth_user_form_submit()
 */
function simplesamlphp_auth_user_form_includes(array &$form) {
  $form['simplesamlphp_auth_user_enable'] = [
    '#type' => 'checkbox',
    '#title' => t('Enable this user to leverage SAML authentication'),
    '#access' => \Drupal::currentUser()
      ->hasPermission('change saml authentication setting'),
    '#description' => t("WARNING: if unchecked, this will become a local Drupal user, which might be denied access based on the SimpleSAMLphp settings for authenticating local Drupal accounts.<br />Don't use this setting for access control, which should be configured in your IdP instead.<br />NOTE: if the configuration option 'Automatically enable SAML authentication for existing users upon successful login' is activated, this Drupal account can become linked with SAML (again) when the user succesfully authenticates to the IdP."),
  ];

  // We store the authname as the initial email. If we're using SimpleSAML we
  // need to enforce an email address.
  $form['account']['mail']['#required'] = TRUE;
  $form['actions']['submit']['#submit'][] = 'simplesamlphp_auth_user_form_submit';
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function simplesamlphp_auth_form_user_login_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Return without executing if the functionality is not enabled.
  $config = \Drupal::config('simplesamlphp_auth.settings');
  \Drupal::service('renderer')
    ->addCacheableDependency($form, $config);
  if (!$config
    ->get('activate') || !$config
    ->get('login_link_show')) {
    return;
  }
  $label = $config
    ->get('login_link_display_name');
  $form['simplesamlphp_auth_login_link'] = [
    '#title' => $label,
    '#type' => 'link',
    '#url' => Url::fromRoute('simplesamlphp_auth.saml_login'),
    '#attributes' => [
      'title' => $label,
      'class' => [
        'simplesamlphp-auth-login-link',
      ],
    ],
  ];
}

/**
 * Form submission handler for user_form.
 *
 * @see simplesamlphp_auth_form_user_register_form_alter()
 * @see simplesamlphp_auth_form_user_form_alter()
 */
function simplesamlphp_auth_user_form_submit($form, FormStateInterface $form_state) {
  $authmap = \Drupal::service('externalauth.authmap');
  $externalauth = \Drupal::service('externalauth.externalauth');

  // Add an authmap entry for this account, so it can leverage SAML
  // authentication.
  if ($form_state
    ->getValue('simplesamlphp_auth_user_enable')) {
    $account = $form_state
      ->getFormObject()
      ->getEntity();

    // Link an authmap entry to this account, if not yet existing.
    // By default, we use the username as authname.
    // This can be altered if needed. See simplesamlphp_auth.api.php for
    // details.
    $authname = $account
      ->getAccountName();
    \Drupal::modulehandler()
      ->alter('simplesamlphp_auth_account_authname', $authname, $account);
    $externalauth
      ->linkExistingAccount($authname, 'simplesamlphp_auth', $account);
  }
  else {
    $authmap
      ->delete($form_state
      ->getValue('uid'));
  }
}