You are here

shib_auth.module in Shibboleth Authentication 6.4

Drupal Shibboleth authentication module.

Copyright (C) 2010 NIIF Institute (http://www.niif.hu)

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

File

shib_auth.module
View source
<?php

/**
 * @file
 * Drupal Shibboleth authentication module.

Copyright (C) 2010 NIIF Institute (http://www.niif.hu)

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/**
 * Configuration handler
 *
 * Stores default configuration values and returns the active configuration
 * parameter or the list of configuration parameters when $list is set to TRUE.
 *
 * @param $variable
 *   The name of the variable.
 *   The name of the variable.
 * @param $list
 *   If set to TRUE, all configuration parameter names are returned. It's only
 *   intended to be used for debug purposes.
 * @return mixed
 *   The matching variable prefixed with shib_auth_, or the list of the module
 *   configuration variables, if $list is TRUE.
 */
function shib_auth_config($variable, $list = FALSE) {

  //building an array with the available variables, and their default values
  static $var_store = array();
  if (empty($var_store)) {
    $var_store = array(
      'account_linking' => FALSE,
      'account_linking_text' => t('Link this account with another identity'),
      'auto_destroy_session' => FALSE,
      'debug_state' => FALSE,
      'define_username' => FALSE,
      'enable_custom_mail' => FALSE,
      'forceauthn' => FALSE,
      'force_https' => FALSE,
      'auto_destroy_session' => FALSE,
      'is_passive' => FALSE,
      'terms_accept' => FALSE,
      'debug_url' => '',
      'terms_ver' => '',
      'terms_url' => '/',
      'wayf_uri' => '/DS',
      'handler_protocol' => 'https',
      'handler_url' => '/Shibboleth.sso',
      'email_variable' => 'HTTP_SHIB_MAIL',
      'username_variable' => 'REMOTE_USER',
      'login_url' => '',
      'logout_url' => url('<front>'),
      'link_text' => t('Shibboleth Login'),
      'full_handler_url' => shib_auth_get_handler_base() . variable_get('shib_auth_wayf_uri', '/DS'),
      'full_logout_url' => shib_auth_get_handler_base() . '/Logout',
    );
  }
  if ($list) {
    return array_keys($var_store);
  }

  //check, if it exists in the array above, and get its value
  if (array_key_exists($variable, $var_store)) {
    return variable_get("shib_auth_{$variable}", $var_store[$variable]);
  }
  else {
    drupal_set_message(t("Function shib_auth_config(%variable) called but %variable doesn't exists.", array(
      '%variable' => $variable,
    )), 'error');
    return FALSE;
  }
}

//function shib_auth_config

/**
 * This footer part executes isPassive script, if the option was checked on the configuration page
 */
function shib_auth_footer() {
  global $user;
  if (!shib_auth_session_valid() && shib_auth_config('is_passive') && !$user->uid) {
    $base = drupal_get_path('module', 'shib_auth');
    $my_settings = array(
      'login_url' => shib_auth_generate_login_url(),
    );
    drupal_add_js(array(
      'shib_auth' => $my_settings,
    ), 'setting');
    drupal_add_js($base . '/isPassive.js', 'module', 'footer');
    print drupal_get_js();
  }
}

//function shib_auth_footer

/**
 * Display help and module information
 * @param path which path of the site we're displaying help
 * @param arg array that holds the current path as would be returned from arg() function
 * @return help text for the path
 */
function shib_auth_help($path, $arg) {
  $output = '';
  switch ($path) {
    case 'admin/help#shib_auth':
      $output = t('<p>The Shibboleth authentication module lets you utilize the advantages of the Single Sign On (SSO) methods.</p>');
      $output .= t('<p>For more help related to Shibboleth and module configuration, see <a href=\\"@wiki\\">NIIF AAI wiki pages</a></p>', array(
        '@wiki' => url('https://wiki.aai.niif.hu/index.php/Drupal_Shibboleth_module'),
      ));
      break;
    case 'admin/user/shib_auth':
      $output = t('<p>The text shown in the block and on other login pages can be changed on the <a href=\\"@block\\">block settings page</a></p>', array(
        '@block' => url('admin/build/block/configure/shib_auth/0'),
      ));
      break;
  }
  return $output;
}

//function shib_auth_help

/**
 * Errors out
 * Example usage:
 *   if (something_bad_happens())
 *   return shib_auth_error("Something bad happened");
 * EXCEPTION WORKAROUND for php4
 * @param msg Error Message
 */
function shib_auth_error($msg = '') {
  drupal_set_message(t("[Shibboleth authentication] %msg", array(
    '%msg' => $msg,
  )), 'error');
  return FALSE;
}

// function shib_auth_error

/** Unset session variables and destroy them
 * @param msg Error Message
 */
function shib_auth_terminate_session($msg = '') {
  global $user;

  // unset all session variables and destroy session
  if (isset($user->uid)) {
    sess_destroy_uid($user->uid);
  }
  $_SESSION = array();
  if ($msg) {
    shib_auth_error($msg);
  }
  session_destroy();
  $user = drupal_anonymous_user();
}

//function shib_auth_terminate_session

/**
 * This function would destroy the session if
 *  * the shib session is expired and auto_destroy_session is enabled
 *  * the username has changed unexpectedly
 * @param username (might be null)
 * @return FALSE if the session was invalid and therefore destroyed
 *         TRUE if either there's a valid shib session or we allow stale Drupal sessions
 */
function shib_auth_session_check($uname) {
  global $user;

  // if the user IS logged in as non-admin, but we're missing Shibboleth identity
  if (!shib_auth_session_valid() && $_SESSION['shib_auth_authentication'] == 'shib_auth' && shib_auth_config('auto_destroy_session') && $user->uid > 1) {
    shib_auth_terminate_session('Your session is expired. Please log in again.');
    return FALSE;
  }
  if (isset($_SESSION['shib_auth_username'])) {
    if ($_SESSION['shib_auth_username'] != $uname && empty($_SESSION['shib_auth_account_linking'])) {

      /*  See SA-CONTRIB-2009-070
          If we reach here, a new federated user was given an existing Drupal session of
          an old user. This can happen when using Single Logout.
          Probably we should try and re-register the new user instead of just kicking him out,
          but for now just terminate the session for safety.
          This means that the new user has to initiate the session twice.
          However, we allow account linking, if the account_linking session variable had been set
          */
      shib_auth_terminate_session();
      return FALSE;
    }
  }
  else {
    if ($uname) {
      $_SESSION['shib_auth_username'] = $uname;
    }
  }
  return TRUE;
}

// function shib_auth_session_check

/**
 * Function to determine whether the called page is in the debug path
 * print_r-s $_SERVER if yes
 */
function shib_auth_debug() {
  global $user;
  $tags = array(
    'pre',
    'b',
    'br',
  );
  if (shib_auth_config('debug_state')) {
    if (drupal_substr($_GET['q'], 0, drupal_strlen(shib_auth_config('debug_url'))) == shib_auth_config('debug_url')) {
      if ($user->uid) {
        $userinfo = array(
          'uid' => $user->uid,
          'name' => $user->name,
          'mail' => $user->mail,
          'roles' => $user->roles,
        );
        $debug_message = '<b>$user:</b><br /><pre>' . print_r($userinfo, TRUE) . '</pre>';
        drupal_set_message(filter_xss($debug_message, $tags));
      }

      // Work around that drupal_set_message() keeps previous messages in $_SESSION
      $session_copy = $_SESSION;
      if (isset($session_copy['messages'])) {
        unset($session_copy['messages']);
      }
      $debug_message = '<b>$_SESSION:</b><br /><pre>' . print_r($session_copy, TRUE) . '</pre>';
      unset($session_copy);

      // end of workaround
      drupal_set_message(filter_xss($debug_message, $tags));
      $debug_message = '<b>$_SERVER:</b><br /><pre>' . print_r($_SERVER, TRUE) . '</pre>';
      drupal_set_message(filter_xss($debug_message, $tags));
      $config_keys = shib_auth_config('', TRUE);
      sort($config_keys);
      $config_copy = array();
      foreach ($config_keys as $key) {
        $config_copy[$key] = shib_auth_config($key);
      }
      $debug_message = filter_xss('<b>MODULE CONFIGURATION:</b><br /><pre>' . print_r($config_copy, TRUE) . '</pre>', $tags);
      drupal_set_message(filter_xss($debug_message, $tags));
    }
  }
}

//function shib_auth_debug

/**
 * Get IdP name
 * @return IdP name
 */
function shib_auth_get_idp() {
  if (isset($_SERVER['Shib-Identity-Provider'])) {
    return $_SERVER['Shib-Identity-Provider'];
  }
  elseif (isset($_SERVER['Shib_Identity_Provider'])) {
    return $_SERVER['Shib_Identity_Provider'];
  }
  elseif (isset($_SERVER['HTTP_SHIB_IDENTITY_PROVIDER'])) {
    return $_SERVER['HTTP_SHIB_IDENTITY_PROVIDER'];
  }
  elseif (isset($_SERVER['HTTP_SHIBIDENTITYPROVIDER'])) {
    return $_SERVER['HTTP_SHIBIDENTITYPROVIDER'];
  }
  return '';
}

//function shib_auth_get_idp

/**
 * Checks whether Shibboleth SP has set the Identity Provider $_SERVER field. It is always set
 * if there is a Shibboleth SP session.
 * @return true if there is a valid Shibboleth session
 */
function shib_auth_session_valid() {
  return (bool) shib_auth_get_idp();
}

// function shib_auth_session_valid

/**
 * @return true if there is a valid Shibboleth session with a Shibboleth SP >= 2.0
 */
function shib_auth_session_isShib2x() {
  if (shib_auth_session_valid()) {
    return isset($_SERVER['Shib-Identity-Provider']) || isset($_SERVER['Shib_Identity_Provider']);
  }
  return FALSE;
}

//function shib_auth_session_isShib2x

/**
 * Saves an entry into shib_authmap and also saves mail if changed
 * A row in the authmap contains the drupal user id, the targeted id from
 * Shibboleth, the IdP name, the date the user was created, and user consent
 * version number.
 * @uname the username got from IdP
 * @custom_uname the customized username
 * @umail_single the e-mail address
 */
function shib_auth_save_authmap($uname, $custom_uname, $umail_single) {
  global $user;
  $email_already_used_query = db_query("SELECT uid FROM {users} WHERE mail='%s'", $umail_single);
  $email_already_used = db_fetch_object($email_already_used_query);

  // If the mail address is used, give an error
  if ($email_already_used && !(!empty($_SESSION['shib_auth_account_linking']) && $email_already_used->uid == $user->uid)) {
    shib_auth_error('[shib_save_authmap] Error saving user account. E-mail address is already used.');
  }
  else {

    //if linking an account with shib: don't login / register again
    if (!($user->uid > 1 && !empty($_SESSION['shib_auth_account_linking']))) {
      if (user_is_blocked($custom_uname)) {

        //register a new user with this username, and login
        shib_auth_error('This user is blocked');
        return;
      }
      user_external_login_register($custom_uname, 'shib_auth');
      if ($user->uid) {
        $null = array();
        user_authenticate_finalize($null);
      }
    }
    if (!user_get_authmaps($user->name)) {
      user_set_authmaps($user, array(
        'auth_shib_auth' => $user->name,
      ));
    }
    $_SESSION['shib_auth_authentication'] = 'shib_auth';
    if (!$user) {

      // Something really bad happened
      shib_auth_error('Fatal error while saving mail address');
      return;
    }
    $idp = shib_auth_get_idp();

    //write an entry into shib_authmap set the current consent version
    $sql = "INSERT INTO {shib_authmap} (uid, targeted_id, idp, created, consentver) VALUES  ('%s', '%s', '%s', '%s', '%s')";
    $result = db_query($sql, $user->uid, $uname, $idp, date('Y-m-d H:i:s'), shib_auth_config('terms_ver'));
    if (!shib_auth_config('enable_custom_mail') || empty($_SESSION['shib_auth_account_linking'])) {

      //rewrite e-mail address
      $user = shib_auth_save_mail($user, $umail_single);
      if (!$user) {

        // Something really bad happened
        shib_auth_error('[shib_auth_save_authmap] Fatal error while saving mail address');
        return;
      }
    }
    if (isset($_SESSION['shib_auth_account_linking']) && $_SESSION['shib_auth_account_linking']) {
      unset($_SESSION['shib_auth_account_linking']);
      drupal_set_message('Account successfully linked to new shibboleth id!');
    }
  }
}

//function shib_auth_save_authmap

/**
 * Login an user based on the shib_authmap informations
 * @uname the username got from IdP
 * @uid drupal user id
 * @umail_single the e-mail address
 * @alreadyloggedin is true if the user has already logged in
 */
function shib_login_authmap($uname, $umail_single, $uid, $alreadyloggedin = False) {
  global $user;

  //Return if we can't login the user because the mail is missing
  if (!shib_auth_config('enable_custom_mail') && !valid_email_address($umail_single)) {
    shib_auth_error('Can\'t fetch mail attribute and it is required by the configuration');
    return;
  }

  //Get the name of the user with the given uid
  $auth_map_un_query = db_query("SELECT name FROM {users} WHERE uid='%s'", $uid);
  $authmap_username = db_fetch_array($auth_map_un_query);

  //We load this account to make operations with
  $account = user_external_load($authmap_username['name']);
  if (isset($account->uid)) {

    //We don't login user again, if there is already one logged in (made redirect loops when linking an account)
    if ($user->uid || user_external_login($account)) {

      //set auth variable to shib_auth
      $_SESSION['shib_auth_authentication'] = 'shib_auth';

      //Shibboleth mail address override was enabled in the admin config
      if (shib_auth_config('enable_custom_mail') == 0) {

        //check if there isn't any user with this e-mail (whose name is different)
        $email_for_other_user_query = db_query("SELECT * FROM {users} WHERE mail='%s' AND uid <> '%s'", $umail_single, $user->uid);
        $email_for_other_user = db_fetch_object($username_and_email_query);
        if ($email_for_other_user) {
          shib_auth_error('[shib_login_authmap] Error saving user account. E-mail address is already used.');
        }
        else {
          $user = shib_auth_save_mail($user, $umail_single);
          if (!$user) {

            // Something really bad happened
            shib_auth_error('[shib_auth_login_authmap] Fatal error while saving mail address');
            return;
          }
        }
      }

      //forward user to login url, if set
      if (shib_auth_config('login_url') != '' && !$alreadyloggedin && $_GET['q'] != shib_auth_config('login_url')) {
        drupal_goto(shib_auth_config('login_url'));
      }
    }
    else {
      shib_auth_error('Couldn\'t login user: ' . $authmap_username['name']);
    }
  }
  else {
    shib_auth_error('Couldn\'t login user: ' . $authmap_username['name']);
  }

  //redirect user to a predefined page, or a page, she wanted to see before clicking on login
  shib_auth_submit_redirect();
}

//function shib_login_authmap

/**
 * Check user identifier
 * @return FALSE if the identifier is not valid
 */
function shib_auth_check_identifier($uname) {
  if (!$uname) {
    $message = 'Username is missing. Please contact your site administrator!';
    shib_auth_error(t('@msg', array(
      '@msg' => $message,
    )));
    watchdog('shib_auth', '@msg', array(
      '@msg' => $message,
    ), WATCHDOG_CRITICAL);
    return FALSE;
  }
  elseif (drupal_strlen($uname) > 255) {
    $message = 'User identifier is too long to process. Please contact your site administrator!';
    shib_auth_error(t('@msg', array(
      '@msg' => $message,
    )));
    watchdog('shib_auth', '@msg', array(
      '@msg' => $message,
    ), WATCHDOG_CRITICAL);
    return FALSE;
  }
  return TRUE;
}

//function shib_auth_check_identifier

/**
 * Load an authmap user object from shib_authmap
 * @return the authmap object(array) if found, null otherwise
 */
function shib_auth_load_from_authmap($uname) {
  $auth_map_query = db_query("SELECT * FROM {shib_authmap} WHERE targeted_id='%s'", $uname);
  return db_fetch_array($auth_map_query);
}

//function shib_auth_load_from_authmap

/**
 * User Data Customization function - MAIL
 * This function handles the mail customization process
 * @uname the username got from IdP
 * @custom_uname the customized username
 * @custom_mail the costumized e-mail address
 */
function shib_auth_custom_mail($uname, $custom_username, $custom_mail) {
  if (!valid_email_address($custom_mail)) {
    shib_auth_error('Please enter a valid e-mail address');
  }
  elseif (shib_auth_config('define_username') == 0) {

    // and email isn't used by another registered drupal user, save user
    shib_auth_save_authmap($uname, $uname, $custom_mail);
  }
  elseif (shib_auth_config('define_username') == 1 && isset($custom_username) && $custom_username) {
    shib_auth_custom_username($uname, $custom_username, $custom_mail);
  }
}

//function shib_auth_custom_mail

/**
 * User Data Customization function - USERNAME
 * This function handles the username customization process
 * @uname the username got from IdP
 * @custom_uname the customized username
 * @umail_single the e-mail address received from IdP
 */
function shib_auth_custom_username($uname, $custom_username, $umail_single) {

  //validate it
  if ($error = user_validate_name($custom_username)) {
    shib_auth_error(filter_xss($error));
  }
  else {

    //check if username already exists
    $un_already_used_query = db_query("SELECT * FROM {users} WHERE name='%s'", $custom_username);
    $un_already_used = db_fetch_object($un_already_used_query);
    if ($un_already_used) {
      shib_auth_error('Error saving user account. User name is already used.');
    }
    else {
      shib_auth_save_authmap($uname, $custom_username, $umail_single);
    }
  }
}

//function shib_auth_custom_username

/**
 * User Data Customization function - Redirect user to the custom form
 * This function redirects user to the custom data form, remembering the url, she wanted to visit before registering or a specific login url, if it is set
 */
function shib_auth_goto_custom_form() {
  if ($_GET['q'] != 'shib_auth/get_custom_data' && $_GET['q'] != 'shib_link') {
    $_SESSION['shib_auth_custom_form'] = TRUE;
    if (shib_auth_config('login_url') == '') {
      $_SESSION['shib_auth_custom_form_url'] = $_GET['q'];
    }
    else {
      $_SESSION['shib_auth_custom_form_url'] = shib_auth_config('login_url');
    }
    drupal_goto('shib_auth/get_custom_data');
  }
}

//function shib_auth_goto_custom_form

/**
 * User Data Customization function - Redirect user to the appropriate page
 * This function redirects user to the url, she wanted to visit before registering or to login url, if it is set
 */
function shib_auth_submit_redirect() {
  if (isset($_SESSION['shib_auth_custom_form_url'])) {
    $redirect_url = $_SESSION['shib_auth_custom_form_url'];
    unset($_SESSION['shib_auth_custom_form_url']);
    drupal_goto($redirect_url);
  }
}

//function shib_auth_submit_redirect

/**
 * Cancel button is pressed, redirect user to shib logout, and then to the page he came from / loginurl
 */
function shib_auth_cancel_custom() {
  $logouthandler = shib_auth_get_redirect_base(shib_auth_config('full_logout_url'));
  if (isset($_SESSION['shib_auth_custom_form_url'])) {
    $redirect_url = url($_SESSION['shib_auth_custom_form_url'], array(
      'absolute' => TRUE,
    ));
    unset($_SESSION['shib_auth_custom_form_url']);
    shib_auth_redirect($logouthandler, $redirect_url);
  }
}

/**
 * If any customization or consent option is enabled, the custom form will show  up before registering
 * and forces the user to accept user consent and define username and/or e-mail address
 * (prefilling fields with the data coming from the IdP)
 * @umail_single e-mail address received from IdP
 * @uname id received from IdP
 */
function shib_auth_custom_form($umail_single = NULL, $uname = NULL) {
  if (isset($_POST['op']) && $_POST['op'] == t('Cancel')) {
    shib_auth_cancel_custom();
  }

  //check if any of the customization options are enabled
  if (shib_auth_config('enable_custom_mail') || $umail_single && shib_auth_config('define_username') || (shib_auth_config('enable_custom_mail') || $umail_single) && shib_auth_config('terms_accept')) {

    // if there already is a POST-ed form, save received values as a variable
    if ($_POST['form_id'] == 'shib_auth_custom_data') {
      if ($_POST['custom_mail']) {
        $custom_mail = $_POST['custom_mail'];
      }
      if ($_POST['custom_username']) {
        $custom_username = $_POST['custom_username'];
      }
      if ($_POST['accept']) {
        $consent_accepted = $_POST['accept'];
      }
    }

    //If the consent is accepted or it isn't configured
    if ($consent_accepted && shib_auth_config('terms_accept') || !shib_auth_config('terms_accept')) {

      // ****** CUSTOM MAIL **********

      //if the user provided the custom mail string on the custom data form, and it is not empty
      if (isset($custom_mail) && $custom_mail) {
        shib_auth_custom_mail($uname, $custom_username, $custom_mail);
      }
      elseif (shib_auth_config('define_username') && isset($custom_username) && $custom_username) {
        shib_auth_custom_username($uname, $custom_username, $umail_single);
      }
      elseif ($consent_accepted && shib_auth_config('terms_accept')) {

        //register user
        shib_auth_save_authmap($uname, $uname, $umail_single);
      }
      else {
        shib_auth_goto_custom_form();
      }
    }
    else {
      shib_auth_goto_custom_form();
    }

    //If everything was fine, the user is logged in, and redirected to the page, which she wanted to open before the auth process had been initiated
    shib_auth_submit_redirect();
    return TRUE;
  }
  else {
    return FALSE;
  }
}

//function shib_auth_custom_form

/**
 * This function updates the accepted consent version number of the user to the current one
 */
function shib_auth_consent_update($uname, $umail_single, $uid) {
  $sql = "UPDATE {shib_authmap} SET consentver='%s' WHERE targeted_id='%s'";
  $result = db_query($sql, shib_auth_config('terms_ver'), $uname);
  shib_login_authmap($uname, $umail_single, $uid);
}

//function shib_auth_consent_update

/**
 * Assign roles to the user's session
 */
function shib_auth_role_assignment() {

  //generate role cache if it doesn't exists
  shib_auth_generate_rolenames(FALSE);
  if (shib_auth_session_valid() && !user_is_anonymous() && empty($_SESSION['shib_auth_account_linking'])) {
    shib_auth_assignroles();
  }
}

/**
 * Create a new user based on informations from the Shibboleth handler if it's necessary or log in.
 *
 * If already authenticated - do nothing
 * If Shibboleth doesn't provide User information - error message
 * Else if user exists, and mail override (shib_auth_req_shib_only) enabled, override existing user info
 * If not exists, and Shibboleth provides mail address, create an account for this user
 * If there's no mail attribute, ask for the mail address on a generated form if mail override (shib_auth_req_shib_only) is disabled
 * In this case, the account will be created with this e-mail address.
 */
function shib_auth_init() {
  global $user;

  //add theme css
  drupal_add_css(drupal_get_path('module', 'shib_auth') . '/shib_auth.css');

  // Make sure that the user module is already loaded.
  drupal_load('module', 'user');
  $consent_accepted = FALSE;

  /* We want to return as early as possible if we have nothing to do.
    But for checking the session, we need the username first (if it's set) */
  $uname = $_SERVER[shib_auth_config('username_variable')];

  // Storing whether the user was already logged in or not
  $alreadyloggedin = user_is_anonymous() ? False : True;

  /* CHECKING THE SESSION
       Here shib_auth_session_check() will destroy the session if
         * the shib session is expired and auto_destroy_session is enabled
         * the username has changed unexpectedly
       Either this happens or we do not have a shib session, we don't have anything to do
       but send out some debug and exit.
    */
  if (!shib_auth_session_check($uname) || !shib_auth_session_valid()) {
    shib_auth_debug();
    return;
  }

  /* Time to retrevie the mail and begin some work */
  $umail = $_SERVER[shib_auth_config('email_variable')];
  $umail_single = preg_replace('/;.*/', '', $umail);

  // get the first one if there're many

  //************ ROLE ASSIGMENT  **************
  shib_auth_role_assignment();

  //**************** DEBUG ********************
  shib_auth_debug();

  // Do nothing if the user is logged in and we're not doing account linking
  if ($user->uid && empty($_SESSION['shib_auth_account_linking'])) {
    return;
  }

  // Do virtually nothing when we need to display the custom data form
  if (isset($_SESSION['shib_auth_custom_form']) && $_SESSION['shib_auth_custom_form']) {
    unset($_SESSION['shib_auth_custom_form']);

    // Display it only once
    return;
  }

  /********* Start the login/registering process **********/

  //check identifier if it exists, and not too long
  if (!shib_auth_check_identifier($uname)) {
    shib_auth_error('Shibboleth authentication process can\'t continue');
    return;
  }

  //check if the old user exists in the shibboleth authmap
  $existing_authmap = shib_auth_load_from_authmap($uname);

  //Check whether CONSENT VERSION is CHANGED, if so, users have to accept it again
  if ($_POST['form_id'] == 'shib_auth_custom_data' && $_POST['accept'] && $_POST['op'] != t('Cancel')) {
    $consent_accepted = (bool) $_POST['accept'];
  }

  //*********** LOGIN EXISTING USER ***************

  //The user exists in the authmap, and the consent version check is switched off, or she/he had accepted the newest consent version

  //Then let the user log in
  if ($existing_authmap && (!shib_auth_config('terms_accept') || $existing_authmap['consentver'] == shib_auth_config('terms_ver'))) {
    if (empty($_SESSION['shib_auth_account_linking'])) {
      shib_login_authmap($uname, $umail_single, $existing_authmap['uid'], $alreadyloggedin);
    }
    else {
      shib_auth_terminate_session('This ID has already been registered, please log in again');
    }
  }
  elseif ($existing_authmap && $consent_accepted) {
    shib_auth_consent_update($uname, $umail_single, $existing_authmap['uid']);
  }
  else {

    //If it is account linking and the terms are accepted or forcing an existing user to accept termsandconditions

    //If we have an e-mail address from the shib server, and there isn't any user with this address, create an account with these infos
    if (!empty($_SESSION['shib_auth_account_linking']) || $umail_single && !shib_auth_config('enable_custom_mail') && !shib_auth_config('define_username') && !shib_auth_config('terms_accept')) {
      shib_auth_save_authmap($uname, $uname, $umail_single);
    }
    elseif ($_GET['q'] == shib_auth_config('terms_url')) {

      //Don't display custom form, let the terms and conditions be displayed
    }
    elseif (shib_auth_custom_form($umail_single, $uname)) {

      //We display custom forms on every page, if the user isn't registered yet
    }
    else {
      shib_auth_error('E-mail address is missing. Please contact your site administrator!');
    }
  }

  //****** ASSIGN ROLES AFTER REGISTER *******
  shib_auth_role_assignment();

  //********* END OF REGISTERING *************
  if (isset($_SESSION['shib_auth_account_linking']) && $_SESSION['shib_auth_account_linking']) {
    unset($_SESSION['shib_auth_account_linking']);
    drupal_set_message('End of account linking session');
  }
}

// function shib_auth_init()

/**
 * Redirects user to the handler url, and handles return option
 * @param handlerurl url of the login / logout handler
 * @param return the path the user will be redirected after Shibboleth processing
 */
function shib_auth_redirect($handlerurl, $return = NULL) {
  $options = array();

  //recognize, if logout handler contains variables
  $handler = explode('?', $handlerurl);
  if (isset($handler[1])) {
    parse_str($handler[1], $options);
  }
  if (isset($return)) {
    $options['return'] = $return;
  }
  drupal_goto($handler[0], $options);
}

// function shib_auth_redirect()
function shib_auth_get_redirect_base($handlerurl) {
  $handlerprotocol = 'http';
  if ($handlerurl[0] == '/') {
    if (isset($_SERVER['HTTPS']) || shib_auth_config('force_https')) {
      $handlerprotocol = 'https';
    }
    return $handlerprotocol . '://' . $_SERVER['HTTP_HOST'] . $handlerurl;
  }
  return $handlerurl;
}

/**
 * Get Shibboleth handler base as an absolute URI (such as https://example.com/Shibboleth.sso)
 * @return handler base
 */
function shib_auth_get_handler_base() {
  $handlerurl = variable_get('shib_auth_handler_url', '/Shibboleth.sso');
  $handlerprotocol = variable_get('shib_auth_handler_protocol', 'https');
  if (preg_match('#^http[s]{0,1}://#', $handlerurl)) {

    // If handlerurl is an absolute path
    return $handlerurl;
  }
  else {

    // Else, if the handlerurl is a relative path
    // If the URI doesn't start with slash then extend it
    if (drupal_substr($handlerurl, 0, 1) != '/') {
      $handlerurl = '/' . $handlerurl;
    }
    return $handlerprotocol . '://' . $_SERVER['HTTP_HOST'] . $handlerurl;
  }
}

//function shib_auth_get_handler_base

/**
 * Let the user exit from the Shibboleth authority when he/she log out from the actual Drupal site.
 * @param op What kind of action is being performed.
 * @param edit The array of form values submitted by the user.
 * @param account The user object on which the operation is being performed.
 * @param category The active category of user information being edited.
 */
function shib_auth_user($op, &$edit, &$account, $category = NULL) {
  global $user;
  if ($op == 'logout') {
    if (isset($_SESSION['shib_auth_authentication']) && $_SESSION['shib_auth_authentication'] === 'shib_auth') {
      unset($_SESSION['shib_auth_rolelog']);
      $logouthandler = shib_auth_get_redirect_base(shib_auth_config('full_logout_url'));
      unset($_SESSION['shib_auth_authentication']);
      $logout_redirect = url(shib_auth_config('logout_url'), array(
        'absolute' => TRUE,
      ));
      shib_auth_redirect($logouthandler, $logout_redirect);
    }
  }
  elseif ($op == 'delete') {
    db_query("DELETE FROM {shib_authmap} WHERE uid = %d", $account->uid);
  }
}

// function shib_auth_user

/**
 * Valid permissions for this module
 * @return array An array of valid permissions for the shib_auth module
 */
function shib_auth_perm() {
  return array(
    'administer shibboleth authentication',
  );
}

// function shib_auth_perm()

/**
 * Generates shibboleth login url based on configuration
 */
function shib_auth_generate_login_url() {
  $queries = $_GET;
  $sep = "?";
  $sessioninitiator = shib_auth_get_redirect_base(shib_auth_config('full_handler_url'));
  $forceauthn = '';

  //append forceAuthN variable to the login string
  if (shib_auth_config('forceauthn')) {
    $forceauthn = '&forceAuthn=1';
  }
  $queries['q'] = 'shib_login/' . $queries['q'];

  //generate the currently viewed url to pass as an url to return from IdP

  //TODO refactor:
  $url_to_return = (isset($_SERVER['HTTPS']) || shib_auth_config('force_https') && drupal_substr($sessioninitiator, 0, 5) == 'https' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . url('', array(
    'query' => $queries,
  ));
  if (preg_match('/\\?/', $sessioninitiator)) {
    $sep = '&';
  }
  return "{$sessioninitiator}" . $sep . "target=" . urlencode($url_to_return) . $forceauthn;
}

//function shib_auth_generate_login_url

/**
 * Generate the menu element to access the Shibboleth authentication module's administration page
 * @returns HTML text of the administer menu element
 */
function shib_auth_menu() {
  $items = array();
  $items['admin/user/shib_auth'] = array(
    'title' => 'Shibboleth settings',
    'description' => 'Settings of the Shibboleth authentication module',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'shib_auth_admin_general',
    ),
    'access arguments' => array(
      'administer shibboleth authentication',
    ),
    'file' => 'shib_auth_forms.inc',
  );
  $items['admin/user/shib_auth/general'] = array(
    'title' => 'General settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'access arguments' => array(
      'administer shibboleth authentication',
    ),
    'weight' => -10,
    'file' => 'shib_auth_forms.inc',
  );
  $items['shib_auth/get_custom_data'] = array(
    'title' => 'Customize drupal user attributes',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'shib_auth_custom_data',
    ),
    'access callback' => 'access_shib_login',
    'type' => MENU_CALLBACK,
    'file' => 'shib_auth_forms.inc',
  );
  $items['shib_login/%'] = array(
    'page callback' => 'shib_auth_login',
    'type' => MENU_CALLBACK,
    'access callback' => 'access_shib_login',
    'file' => 'shib_auth_forms.inc',
  );
  $items['shib_link'] = array(
    'page callback' => 'shib_auth_account_link',
    'type' => MENU_CALLBACK,
    'access arguments' => array(
      'access content',
    ),
    'file' => 'shib_auth_forms.inc',
  );
  $items['admin/user/shib_auth/advanced'] = array(
    'title' => 'Advanced settings',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'shib_auth_admin_advanced',
    ),
    'access arguments' => array(
      'administer shibboleth authentication',
    ),
    'weight' => -6,
    'file' => 'shib_auth_forms.inc',
  );

  /******* ROLE-RELATED MENU ITEMS ********/
  $items['admin/user/shib_auth/rules'] = array(
    'title' => 'Shibboleth group rules',
    'description' => 'Administer attribute-based role assignment',
    'page callback' => '_shib_auth_list_rules',
    'page arguments' => array(
      'shib_auth_list_rules',
    ),
    'access arguments' => array(
      'administer permissions',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => -8,
    'file' => 'shib_auth_roles_forms.inc',
  );
  $items['admin/user/shib_auth/new'] = array(
    'title' => 'Add new rule',
    'description' => 'Add new attribute-based role assignment rule',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'shib_auth_new_rule',
    ),
    'access arguments' => array(
      'administer permissions',
    ),
    'type' => MENU_NORMAL_ITEM,
    'weight' => -7,
    'file' => 'shib_auth_roles_forms.inc',
  );
  $items['admin/user/shib_auth/delete/%'] = array(
    'title' => 'Delete rule',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      '_shib_auth_rule_delete_confirm_form',
      4,
    ),
    'access arguments' => array(
      'administer permissions',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'shib_auth_roles_forms.inc',
  );
  $items['admin/user/shib_auth/edit/%'] = array(
    'title' => 'Edit rule',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'shib_auth_edit_rule',
      4,
    ),
    'access arguments' => array(
      'administer permissions',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'shib_auth_roles_forms.inc',
  );
  $items['admin/user/shib_auth/clone/%'] = array(
    'title' => 'Clone rule',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      '_shib_auth_rule_clone_confirm_form',
      4,
    ),
    'access arguments' => array(
      'administer permissions',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'shib_auth_roles_forms.inc',
  );
  return $items;
}

// function shib_auth_menu

/**
 * Generate the HTML text for the shib_auth login block
 * @param op the operation from the URL
 * @param delta offset
 * @returns block HTML
 */
function shib_auth_block($op = 'list', $delta = 0, $edit = array()) {

  // listing of blocks, such as on the admin/block page
  switch ($op) {
    case 'list':
      $blocks[0] = array(
        'info' => t('Shibboleth authentication'),
        'status' => TRUE,
        'visibility' => 1,
        'weight' => 0,
        'region' => 'left',
      );
      return $blocks;
    case 'configure':
      $form = array();
      switch ($delta) {
        case 0:
          $form['shib_auth_link_text'] = array(
            '#type' => 'textfield',
            '#title' => t('Text of the auth link'),
            '#require' => TRUE,
            '#size' => 60,
            '#description' => t('Here you can replace the text of the authentication link.'),
            '#default_value' => shib_auth_config('link_text'),
          );
      }
      return $form;
    case 'save':
      switch ($delta) {
        case 0:
          variable_set('shib_auth_link_text', $edit['shib_auth_link_text']);
      }
      break;
    case 'view':
    default:
      switch ($delta) {
        case 0:
          $block = array(
            'subject' => t('Shibboleth login'),
            'content' => _login_url_html(),
          );
          break;
      }
      return $block;
  }
}

// function shib_auth_block()

/**
 * Generate the login block content if user not logged in
 */
function _login_url_html($is_account_linking = FALSE) {
  global $user;
  if (!$user->uid and $is_account_linking === FALSE) {
    return theme('shib_login_block', shib_auth_generate_login_url(), shib_auth_config('link_text'));
  }
  elseif ($user->uid > 1 and $is_account_linking and shib_auth_config('account_linking')) {
    return theme('shib_login_block', url('shib_link'), shib_auth_config('account_linking_text'));
  }
  return NULL;
}

//function _login_url_html

/**
 * Alters user_login form for the shibboleth authentication module.
 *
 * @param $form The form.
 * @param $form_state contains all of the data of the form
 * @param $form_id The form ID.
 */
function shib_auth_form_alter(&$form, &$form_state, $form_id) {
  global $user;
  if ($form_id == 'user_login') {
    $form['shibboleth'] = array(
      '#type' => 'markup',
      '#weight' => -20,
      '#value' => _login_url_html(),
    );
  }
  if ($form_id == 'user_profile_form' && $form['#parameters'][2]->uid == $user->uid) {
    $form['shibboleth'] = array(
      '#type' => 'markup',
      '#weight' => -1,
      '#value' => _login_url_html(TRUE),
    );
  }
  if ($form_id == 'user_admin_role') {
    shib_auth_generate_rolenames(TRUE);
  }
}

//function shib_auth_form_alter

/**
 * Dummy access argument function
 */
function access_shib_login() {
  return shib_auth_session_valid();
}

//function access_shib_login

/**
 * Implement of the hook_theme()
 */
function shib_auth_theme($existing, $type, $theme, $path) {
  return array(
    'shib_login_block' => array(
      'template' => 'shib_login_block',
      'arguments' => array(
        'login_url' => NULL,
        'login_text' => NULL,
      ),
    ),
  );
}

//function shib_auth_theme

/**************** ROLE ASSIGNMENT FUNCTIONS  *******************/

/**
 * The admin can define authorization rules based on the server variables
 * (possibly provided by Shibboleth IdP) to give roles to users.
 * The rules can be defined as a [server field - Regexp - role(s)] triplet
 */
function shib_auth_assignroles() {
  global $user;
  $profile_changed = 0;

  // Store roles for further examination
  $former_roles = serialize($user->roles);

  // Sticky rules come first
  $rules = db_query("SELECT * FROM {shib_auth} ORDER BY sticky DESC");
  while ($rule = db_fetch_array($rules)) {
    if ($profile_changed && !$rule['sticky']) {

      // This is the first non-sticky rule, and sticky rules have modified the user's roles
      shib_auth_save_roles();
      $profile_changed = 0;
    }
    $profile_changed += shib_auth_process_rule($rule);

    // Only sticky rules return >0
  }
  if ($profile_changed) {

    // must do this in case there's no non-sticky rule
    shib_auth_save_roles();
  }
  $user->roles = array_filter($user->roles);

  // If the user roles array has been changed then reset the permission cache
  if (serialize($user->roles) != $former_roles) {

    // Hack to reset the permissions
    user_access('access content', $account, TRUE);
  }
  $_SESSION['shib_auth_rolelog'] = '1';
}

//function shib_auth_assignroles

/**
 * This function processes role assignment rules
 * The function matches rule regular expressions with defined server variables
 * If there is a match, it assigns roles to the user logged in
 * @rule the id of the rule currently processed
 */
function shib_auth_process_rule($rule) {
  global $user;
  $profile_changed = 0;

  // is a constant 0 when the rule is not a sticky one
  $fieldname = $rule['field'];
  $expression = '/' . urldecode($rule['regexpression']) . '/';

  // if the given server field exists
  if (isset($_SERVER[$fieldname])) {
    foreach (explode(';', $_SERVER[$fieldname]) as $value) {

      //check if the RegEx fits to one of the value of the server field
      if (preg_match($expression, trim($value))) {
        $roles = unserialize($rule['role']);

        // there is a match, so give this user the specified role(s)
        if (empty($roles)) {

          // null-rule, NOP
          return NULL;
        }
        foreach ($roles as $role_id) {
          $role_name = shib_auth_get_rolename($role_id);
          if ($user->roles[$role_id] == $role_name) {
            continue;

            // NOP if the user already has the given role
          }
          $user->roles[$role_id] = $role_name;
          if ($rule['sticky']) {

            // Sticky rules change the profile
            $profile_changed = 1;
            if (!isset($_SESSION['shib_auth_rolelog'])) {
              watchdog('shib_grant_stick', 'Role "@id" has been permanently granted', array(
                '@id' => $role_name,
              ), WATCHDOG_NOTICE);
            }
          }
          else {
            if (!isset($_SESSION['shib_auth_rolelog'])) {
              watchdog('shib_grant_role', 'Role "@id" has been granted', array(
                '@id' => $role_name,
              ), WATCHDOG_NOTICE);
            }
          }
        }
      }
    }
  }
  return $profile_changed;
}

//function shib_auth_process_rule

/**
 *  Unfortunately if we called user_save() on updating roles, we would possibly lose profile fields.
 *  Therefore we hack with the {users_roles} table
 */
function shib_auth_save_roles() {
  global $user;

  // We won't modify system users
  if (!$user->uid || $user->uid <= 1) {
    return;
  }
  if (isset($user->roles)) {
    db_query('DELETE FROM {users_roles} WHERE uid = %d', $user->uid);
    foreach (array_keys($user->roles) as $rid) {
      if (!in_array($rid, array(
        DRUPAL_ANONYMOUS_RID,
        DRUPAL_AUTHENTICATED_RID,
      ))) {
        db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $user->uid, $rid);
      }
    }
  }
}

//function shib_auth_save_roles

/**
 *  Unfortunately if we called user_save() on updating email, we would possibly lose
 *  profile fields, so we are forced to hack with the {users} table
 *  @param $account user account to modify
 *  @param $mail new mail address
 *  @return modified user object or FALSE on error, just like user_save would do
 */
function shib_auth_save_mail($account, $mail) {

  // Basic parameter checks to avoid do anything fatal
  if (!is_object($account) || !$account->uid || $account->uid <= 1) {
    return FALSE;
  }
  if (!valid_email_address($mail)) {
    shib_auth_error('E-mail address is invalid');
    return FALSE;
  }

  // If the mail hasn't changed, do not do anything
  if ($account->mail && $account->mail == $mail) {
    return $account;
  }

  // We deliberately do not call user_module_invoke('update',..) here, because
  // profile module seems to take it too seriously and wipes out profile fields
  // Cross fingers, do the update
  $success = db_query("UPDATE {users} SET mail = '%s' WHERE uid = %d", $mail, $account->uid);
  if (!$success) {

    // Failed for some reason?
    shib_auth_error('Error saving mail');
    return FALSE;
  }

  // Refresh user object
  $account->mail = $mail;

  // We can call things hooked on after_update
  $null = NULL;
  user_module_invoke('after_update', $null, $account);
  return $account;
}

// function shib_auth_save_mail

/**
 * This function gets the username
 * @param $force If it is TRUE, the cache is regenerated
 */
function shib_auth_generate_rolenames($force) {
  if (!isset($_SESSION['shib_auth_rolecache']) || $force) {
    $_SESSION['shib_auth_rolecache'] = array();
    $query = db_query('SELECT rid, name FROM {role}');
    while ($item = db_fetch_array($query)) {
      $rolecache[$item['rid']] = $item['name'];
    }
    $_SESSION['shib_auth_rolecache'] = $rolecache;
  }
}

//function shib_auth_generate_rolenames

/**
 * This function gets the username
 * @param $rid the id of the role
 * @return the role name from the cache
 */
function shib_auth_get_rolename($rid) {
  return $_SESSION['shib_auth_rolecache'][$rid];
}

//function shib_auth_get_rolename

Functions

Namesort descending Description
access_shib_login Dummy access argument function
shib_auth_assignroles The admin can define authorization rules based on the server variables (possibly provided by Shibboleth IdP) to give roles to users. The rules can be defined as a [server field - Regexp - role(s)] triplet
shib_auth_block Generate the HTML text for the shib_auth login block
shib_auth_cancel_custom Cancel button is pressed, redirect user to shib logout, and then to the page he came from / loginurl
shib_auth_check_identifier Check user identifier
shib_auth_config Configuration handler
shib_auth_consent_update This function updates the accepted consent version number of the user to the current one
shib_auth_custom_form If any customization or consent option is enabled, the custom form will show up before registering and forces the user to accept user consent and define username and/or e-mail address (prefilling fields with the data coming from the…
shib_auth_custom_mail User Data Customization function - MAIL This function handles the mail customization process @uname the username got from IdP @custom_uname the customized username @custom_mail the costumized e-mail address
shib_auth_custom_username User Data Customization function - USERNAME This function handles the username customization process @uname the username got from IdP @custom_uname the customized username @umail_single the e-mail address received from IdP
shib_auth_debug Function to determine whether the called page is in the debug path print_r-s $_SERVER if yes
shib_auth_error Errors out Example usage: if (something_bad_happens()) return shib_auth_error("Something bad happened"); EXCEPTION WORKAROUND for php4
shib_auth_footer This footer part executes isPassive script, if the option was checked on the configuration page
shib_auth_form_alter Alters user_login form for the shibboleth authentication module.
shib_auth_generate_login_url Generates shibboleth login url based on configuration
shib_auth_generate_rolenames This function gets the username
shib_auth_get_handler_base Get Shibboleth handler base as an absolute URI (such as https://example.com/Shibboleth.sso)
shib_auth_get_idp Get IdP name
shib_auth_get_redirect_base
shib_auth_get_rolename This function gets the username
shib_auth_goto_custom_form User Data Customization function - Redirect user to the custom form This function redirects user to the custom data form, remembering the url, she wanted to visit before registering or a specific login url, if it is set
shib_auth_help Display help and module information
shib_auth_init Create a new user based on informations from the Shibboleth handler if it's necessary or log in.
shib_auth_load_from_authmap Load an authmap user object from shib_authmap
shib_auth_menu Generate the menu element to access the Shibboleth authentication module's administration page @returns HTML text of the administer menu element
shib_auth_perm Valid permissions for this module
shib_auth_process_rule This function processes role assignment rules The function matches rule regular expressions with defined server variables If there is a match, it assigns roles to the user logged in @rule the id of the rule currently processed
shib_auth_redirect Redirects user to the handler url, and handles return option
shib_auth_role_assignment Assign roles to the user's session
shib_auth_save_authmap Saves an entry into shib_authmap and also saves mail if changed A row in the authmap contains the drupal user id, the targeted id from Shibboleth, the IdP name, the date the user was created, and user consent version number. @uname the username got…
shib_auth_save_mail Unfortunately if we called user_save() on updating email, we would possibly lose profile fields, so we are forced to hack with the {users} table
shib_auth_save_roles Unfortunately if we called user_save() on updating roles, we would possibly lose profile fields. Therefore we hack with the {users_roles} table
shib_auth_session_check This function would destroy the session if
shib_auth_session_isShib2x
shib_auth_session_valid Checks whether Shibboleth SP has set the Identity Provider $_SERVER field. It is always set if there is a Shibboleth SP session.
shib_auth_submit_redirect User Data Customization function - Redirect user to the appropriate page This function redirects user to the url, she wanted to visit before registering or to login url, if it is set
shib_auth_terminate_session Unset session variables and destroy them
shib_auth_theme Implement of the hook_theme()
shib_auth_user Let the user exit from the Shibboleth authority when he/she log out from the actual Drupal site.
shib_login_authmap Login an user based on the shib_authmap informations @uname the username got from IdP @uid drupal user id @umail_single the e-mail address @alreadyloggedin is true if the user has already logged in
_login_url_html Generate the login block content if user not logged in