You are here

cas.module in CAS 6.3

Enables users to authenticate via a Central Authentication Service (CAS) Cas will currently work if the auto registration is turned on and will create user accounts automatically.

File

cas.module
View source
<?php

/**
 * @file
 * Enables users to authenticate via a Central Authentication Service (CAS)
 * Cas will currently work if the auto registration is turned on and will
 * create user accounts automatically.
 */
define('CAS_NO_LINK', 0);
define('CAS_ADD_LINK', 1);
define('CAS_MAKE_DEFAULT', 2);
define('CAS_LOGIN_INVITE_DEFAULT', 'Log in using CAS');
define('CAS_LOGIN_DRUPAL_INVITE_DEFAULT', 'Cancel CAS login');
define('CAS_LOGIN_REDIR_MESSAGE', 'You will be redirected to the secure CAS login page.');
define('CAS_EXCLUDE', 'services/*');

/**
 * Implementation of hook_init().
 *
 * Traps a page load to see if authentication is required.
 */
function cas_init() {
  global $user;
  if (module_exists('cas_test') && arg(0) == 'cas_test') {

    // We are destined for a page handled by the cas_test module, so do not
    // do any processing here. Necessary for CAS gateway tests.
    return;
  }

  // Process a single-sign out request.
  _cas_single_sign_out_check();

  // If user is logged in, redirect 'cas' to '<front>' instead of giving 403.
  if ($user->uid && $_GET['q'] == 'cas') {
    drupal_goto('');
  }

  // If a user is not logged in, consider using CAS authentication.
  if (!$user->uid) {
    $force_authentication = _cas_force_login();
    $check_authentication = _cas_allow_check_for_login();
    if ($force_authentication || $check_authentication) {
      cas_login_check($force_authentication);
    }
  }
}

/**
 * Checks to see if the user needs to be logged in.
 *
 * @param $force_authentication
 *   If TRUE, require that the user be authenticated with the CAS server
 *   before proceeding. Otherwise, check with the CAS server to see if the
 *   user is already logged in.
 */
function cas_login_check($force_authentication = TRUE) {
  global $user;
  if ($user->uid) {

    //Don't Login  because we already are
    return;
  }
  if (!cas_phpcas_load()) {

    // No need to print a message, as the user will already see the failed
    // include_once calls.
    return;
  }

  // Start a Drupal session, if necessary.
  if (function_exists('drupal_session_start')) {

    // PressFlow (and D7) require manually starting the session. Failure to
    // do so will result in an infinite redirection loop as phpCAS requires
    // a valid session to complete the authentication process.
    drupal_session_start();
  }
  _cas_single_sign_out_save_ticket();

  // We use this later for CAS 3 logoutRequests
  // Initialize phpCAS.
  cas_phpcas_init();

  // We're going to try phpCAS auth test
  if ($force_authentication) {
    phpCAS::forceAuthentication();
  }
  else {
    $logged_in = phpCAS::checkAuthentication();

    // Set the login tested cookie
    setcookie('cas_login_checked', 'true');

    // We're done cause we're not logged in.
    if (!$logged_in) {
      return;
    }
  }

  // Build the cas_user object and allow modules to alter it.
  $cas_user = array(
    'name' => phpCAS::getUser(),
    'login' => TRUE,
    'register' => variable_get('cas_user_register', TRUE),
    'attributes' => cas_phpcas_attributes(),
  );
  drupal_alter('cas_user', $cas_user);

  // Bail out if a module denied login access for this user or unset the user
  // name.
  if (empty($cas_user['login']) || empty($cas_user['name'])) {

    // Only set a warning if we forced login.
    if ($force_authentication) {
      drupal_set_message(t('The user account %name is not available on this site.', array(
        '%name' => $cas_user['name'],
      )), 'error');
    }
    return;
  }

  // Proceed with the login process, using the altered CAS username.
  $cas_name = $cas_user['name'];

  // blocked user check
  $blocked = FALSE;
  if (_cas_external_user_is_blocked($cas_name)) {
    $blocked = 'The username %cas_name has been blocked.';
  }
  elseif (drupal_is_denied('user', $cas_name)) {

    // denied by access controls
    $blocked = 'The name %cas_name is a reserved username.';
  }
  if ($blocked) {

    // Only display error messages only if the user intended to log in.
    if ($force_authentication) {
      watchdog('cas', $blocked, array(
        '%cas_name' => $cas_name,
      ), WATCHDOG_WARNING);
      drupal_set_message(t($blocked, array(
        '%cas_name' => $cas_name,
      )), 'error');
    }
    return;
  }
  $account = cas_user_load_by_name($cas_name);

  // Automatic user registration.
  if (!$account && $cas_user['register']) {

    // No account could be found and auto registration is enabled, so attempt
    // to register a new user.
    $account = cas_user_register($cas_name);
    if (!$account) {

      // The account could not be created, set a message.
      if ($force_authentication) {
        drupal_set_message(t('A new account could not be created for %cas_name. The username is already in use on this site.', array(
          '%cas_name' => $cas_name,
        )), 'error');
      }
      return;
    }
  }

  // final check to make sure we have a good user
  if ($account && $account->uid > 0) {

    // Save the altered CAS name for future use.
    $_SESSION['cas_name'] = $cas_name;
    $cas_first_login = !$account->login;

    // Save single sign out information
    if (!empty($_SESSION['cas_ticket'])) {
      _cas_single_sign_out_save_token($account);
    }

    // Populate $edit with some basic properties.
    $edit['cas_user'] = $cas_user;
    $edit['roles'] = $account->roles + cas_roles();
    if (module_exists('persistent_login') && $_SESSION['cas_remember']) {
      $edit['persistent_login'] = 1;
    }

    // Allow other modules to make their own custom changes.
    cas_user_module_invoke('presave', $edit, $account);

    // Clean up extra variables before saving.
    unset($edit['cas_user']);

    // Save the user account and log the user in.
    $user = user_save($account, $edit);
    user_authenticate_finalize($edit);
    drupal_set_message(t(variable_get('cas_login_message', 'Logged in via CAS as %cas_username.'), array(
      '%cas_username' => $user->name,
    )));
    if (!empty($edit['persistent_login'])) {
      drupal_set_message(t('You will remain logged in on this computer even after you close your browser.'));
    }
    cas_login_page($cas_first_login);
  }
  else {
    $user = drupal_anonymous_user();

    // Only display error messages only if the user intended to log in.
    if ($force_authentication) {
      drupal_set_message(t('No account found for %cas_name.', array(
        '%cas_name' => $cas_name,
      )), 'error');
    }
  }
}

/**
 * Loads the phpCAS library.
 *
 * @param $path
 *   Attempt to load phpCAS using this path. If omitted, phpCAS will be loaded
 *   using Libraries API or the configured value.
 *
 * @return
 *   The phpCAS version if the phpCAS was successfully loaded, FALSE otherwise.
 */
function cas_phpcas_load($path = NULL) {
  if (!isset($path)) {
    if (module_exists('libraries')) {
      $path = libraries_get_path('CAS');
    }
    else {
      $path = variable_get('cas_library_dir', 'CAS');
    }
  }

  // Build the name of the file to load.
  if ($path != '') {
    $path = rtrim($path, '/') . '/';
  }
  $filename = $path . 'CAS.php';
  include_once $filename;
  if (!defined('PHPCAS_VERSION') || !class_exists('phpCAS')) {

    // The file could not be loaded successfully.
    return FALSE;
  }
  return PHPCAS_VERSION;
}

/**
 * Initialize phpCAS.
 *
 * Will load phpCAS if necessary.
 *
 * @param $force
 *   phpCAS cannot be initialized twice. If you need to force this function
 *   to run again, set this to TRUE.
 */
function cas_phpcas_init($force = FALSE) {
  if (!defined('PHPCAS_VERSION') || !class_exists('phpCAS')) {
    cas_phpcas_load();
  }
  static $initialized = FALSE;
  if ($initialized && !$force) {
    return;
  }
  $initialized = TRUE;

  // Variable set
  $server_version = (string) variable_get('cas_version', '2.0');
  $server_cas_server = (string) variable_get('cas_server', 'sso-cas.univ-rennes1.fr');
  $server_port = (int) variable_get('cas_port', '443');
  $server_uri = (string) variable_get('cas_uri', '');
  $cas_cert = (string) variable_get('cas_cert', '');
  $debug_file = (string) variable_get('cas_debugfile', '');
  if ($debug_file != '') {
    phpCAS::setDebug($debug_file);
  }
  $start_session = (bool) FALSE;
  if (variable_get('cas_proxy', 0)) {
    phpCAS::proxy($server_version, $server_cas_server, $server_port, $server_uri, $start_session);
    $cas_pgt_storage_path = variable_get('cas_pgtpath', '');
    if ($cas_pgt_storage_path != '') {
      if (version_compare(PHPCAS_VERSION, '1.3', '>=')) {
        phpCAS::setPGTStorageFile($cas_pgt_storage_path);
      }
      else {
        $cas_pgt_format = variable_get('cas_pgtformat', 'plain');
        phpCAS::setPGTStorageFile($cas_pgt_format, $cas_pgt_storage_path);
      }
    }
  }
  else {
    phpCAS::client($server_version, $server_cas_server, $server_port, $server_uri, $start_session);
  }

  // force CAS authentication
  if ($cas_cert = variable_get('cas_cert', '')) {
    phpCAS::setCasServerCACert($cas_cert);
  }
  else {
    phpCAS::setNoCasServerValidation();
  }
  $service = isset($_GET['q']) ? $_GET['q'] : 'cas';
  phpCAS::setFixedServiceURL(url($service, array(
    'query' => cas_login_destination(),
    'absolute' => TRUE,
  )));

  // Allow other modules to call phpCAS routines. We do not call
  // drupal_alter() since there are no parameters to pass.
  module_invoke_all('cas_phpcas_alter');
}

/**
 * Implementation of hook_perm().
 */
function cas_perm() {
  return array(
    'administer cas',
  );
}

/**
 * Implementation of hook_help().
 */
function cas_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t("Allows users to authenticate via a Central Authentication Service.");
  }
}

/**
 * Implementation of hook_menu().
 */
function cas_menu() {
  global $user;
  $items = array();

  //cas_login_check();
  $items['admin/user/cas'] = array(
    'title' => 'CAS settings',
    'description' => 'Configure central authentication services',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'cas_admin_settings',
    ),
    'access arguments' => array(
      'administer cas',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'cas.admin.inc',
  );
  $items['admin/user/cas/settings'] = array(
    'title' => 'CAS',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/user/user/cas/create'] = array(
    'title' => 'Add CAS user',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'cas_add_user_form',
    ),
    'access arguments' => array(
      'administer users',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'cas.user.inc',
    'tab_parent' => 'admin/user/user',
    'weight' => 1,
  );
  $items['user/%user/cas'] = array(
    'title' => 'CAS',
    'page callback' => 'cas_user_identities',
    'page arguments' => array(
      1,
    ),
    'access arguments' => array(
      'administer users',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'cas.pages.inc',
    'weight' => 1,
  );
  $items['user/%user/cas/delete'] = array(
    'title' => 'Delete CAS username',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'cas_user_delete_form',
      1,
    ),
    'access arguments' => array(
      'administer users',
    ),
    'file' => 'cas.pages.inc',
  );
  $items['cas'] = array(
    'path' => 'cas',
    'title' => 'CAS Login',
    'page callback' => 'cas_login_page',
    'access callback' => 'user_is_anonymous',
    'type' => MENU_SUGGESTED_ITEM,
  );
  $items['caslogout'] = array(
    'title' => 'CAS Logout',
    'page callback' => 'cas_logout',
    'access callback' => 'cas_user_is_logged_in',
    'type' => MENU_SUGGESTED_ITEM,
  );
  return $items;
}
function cas_user_is_logged_in() {
  return user_is_logged_in() || !empty($_SESSION['phpCAS']['user']);
}

/**
 * Implements hook_menu_link_alter().
 *
 * Flag this link as needing alter at display time.
 * @see cas_translated_menu_link_alter()
 **/
function cas_menu_link_alter(&$item, $menu) {
  if ($item['link_path'] == 'cas' || $item['link_path'] == 'caslogout') {
    $item['options']['alter'] = TRUE;
  }
}

/**
 * Implements hook_translated_menu_item_alter().
 *
 * Append dynamic query 'destination' to several menu items.
 **/
function cas_translated_menu_link_alter(&$item, $map) {
  if ($item['href'] == 'cas') {
    $item['localized_options']['query'] = drupal_get_destination();
  }
  elseif ($item['href'] == 'caslogout' && !variable_get('cas_logout_destination', '')) {
    $item['localized_options']['query'] = drupal_get_destination();
  }
}

/**
 * Helper function to rewrite the destination to avoid redirecting to login page after login.
 *
 * Instead of the login page, we redirect to the front page.
 */
function cas_login_destination() {

  // Equivalent to user_login_destination(), which appeared first in Drupal 6.19.
  $destination = drupal_get_destination();
  $destination = $destination == 'destination=user%2Flogin' ? 'destination=user' : $destination;
  return $destination == 'destination=cas' ? 'destination=' : $destination;
}

/**
 * Implements hook_user_operations().
 */
function cas_user_operations($form = array(), $form_state = array()) {
  $operations['cas_create'] = array(
    'label' => t('Create CAS username'),
    'callback' => 'cas_user_operations_create_username',
  );
  $operations['cas_remove'] = array(
    'label' => t('Remove CAS usernames'),
    'callback' => 'cas_user_operations_remove_usernames',
  );
  return $operations;
}

/**
 * Callback function to assign a CAS username to the account.
 *
 * @param $uids
 *   An array of user ids. For each account, a CAS username is created with
 *   the same name as the Drupal username.
 *
 * @see cas_user_operations()
 */
function cas_user_operations_create_username($uids) {
  foreach ($uids as $uid) {
    $account = user_load(array(
      'uid' => (int) $uid,
    ));
    $count = db_result(db_query("SELECT COUNT(*) FROM {cas_user} c WHERE c.uid <> %d AND c.cas_name = '%s'", $account->uid, $account->name));
    if ($count) {
      drupal_set_message(t('CAS username %username already in use.', array(
        '%username' => $account->name,
      )), 'error');
      continue;
    }
    @db_query("INSERT INTO {cas_user} (uid, cas_name) VALUES (%d, '%s')", $account->uid, $account->name);
  }
}

/**
 * Callback function to remove CAS usernames from the account.
 *
 * @param $uids
 *   An array of user ids. For each account, all CAS usernames are removed.
 *
 * @see cas_user_operations()
 */
function cas_user_operations_remove_usernames($uids) {
  foreach ($uids as $uid) {
    db_query("DELETE FROM {cas_user} WHERE uid = %d", $uid);
  }
}

/**
* Implementation of hook_user().
*
* Delete:
*   When a CAS user is deleted, we need to clean up the entry in {cas_user}.
* Insert:
*   When a user is created, record their CAS username if provided.
* Load:
*   Adds an associative array 'cas_names' to each user. The array keys are
*   unique authentication mapping ids, with CAS usernames as the values.
* Update:
*   When a user is updated, change their CAS username if provided.
*/
function cas_user($op, &$edit, &$account) {
  if ($op == 'delete') {
    db_query("DELETE FROM {cas_user} WHERE uid = %d", $account->uid);
  }
  elseif ($op == 'insert') {
    if (!empty($edit['cas_name'])) {
      db_query("INSERT INTO {cas_user} (uid, cas_name) VALUES (%d, '%s')", $account->uid, $edit['cas_name']);
    }
    $edit['cas_name'] = NULL;
  }
  elseif ($op == 'load') {
    $account->cas_names = array();
    $result = db_query('SELECT aid, cas_name FROM {cas_user} WHERE uid = %d', $account->uid);
    while ($record = db_fetch_object($result)) {
      $account->cas_names[$record->aid] = $record->cas_name;
    }
    $account->cas_name = reset($account->cas_names);
  }
  elseif ($op == 'update') {
    if (!array_key_exists('cas_name', $edit)) {

      // If the cas_name key is not provided, there is nothing to do.
      return;
    }
    $cas_name = $edit['cas_name'];

    // See if the user currently has any CAS names.
    reset($account->cas_names);
    if ($aid = key($account->cas_names)) {

      // The user already has CAS username(s) set.
      if (empty($cas_name)) {

        // Remove a CAS username.
        db_query("DELETE FROM {cas_user} WHERE uid = %d AND aid = %d", $account->uid, $aid);
      }
      else {

        // Change a CAS username.
        if ($cas_name != $account->cas_names[$aid]) {
          db_query("UPDATE {cas_user} SET cas_name = '%s' WHERE aid = %d", $cas_name, $aid);
        }
      }
    }
    else {

      // No current CAS usernames.
      if (!empty($cas_name)) {

        // Add a CAS username.
        db_query("INSERT INTO {cas_user} (uid, cas_name) VALUES (%d, '%s')", $account->uid, $edit['cas_name']);
      }
    }
    $edit['cas_name'] = NULL;
  }
}

/**
 * Fetch a user object by CAS name.
 *
 * @param $cas_name
 *   The name of the CAS user.
 * @param $alter
 *   If TRUE, run the CAS username through hook_cas_user_alter() before
 *   loading the account.
 *
 * @return
 *   A fully-loaded $user object upon successful user load or FALSE if user
 *   cannot be loaded.
 */
function cas_user_load_by_name($cas_name, $alter = FALSE) {
  if ($alter) {
    $cas_user = array(
      'name' => $cas_name,
      'login' => TRUE,
      'register' => FALSE,
    );
    drupal_alter('cas_user', $cas_user);
    $cas_name = $cas_user['name'];
  }
  $result = db_query("SELECT uid FROM {cas_user} WHERE LOWER(cas_name) = LOWER('%s')", $cas_name);
  if ($uid = db_fetch_array($result)) {
    return user_load($uid);
  }
  return FALSE;
}

/**
 * Redirects to appropriate page based on user settings.
 *
 * @param $cas_first_login
 *   TRUE if the user was just registered and they should be redirected to the
 *   configured 'Initial login landing page'.
 */
function cas_login_page($cas_first_login = FALSE) {
  global $user;
  $destination = '';
  $query = array();

  // If it is the user's first CAS login and initial login redirection is enabled, go to the set page
  if ($cas_first_login && variable_get('cas_first_login_destination', '')) {
    $destination = variable_get('cas_first_login_destination', '');
    if (isset($_REQUEST['destination'])) {
      $query['destination'] = $_REQUEST['destination'];
    }
    unset($_REQUEST['destination']);
  }

  // Respect the query string, if transmitted.
  drupal_goto($destination, $query);
}

/**
 * Logs a user out of Drupal and then out of CAS.
 *
 * This function does not return, but instead immediately redirects the user
 * to the CAS server to complete the CAS logout process.
 *
 * Other modules intending to call this from their implementation of
 * hook_user('logout') will need to pass $invoke_hook = FALSE to avoid an
 * infinite recursion. WARNING: since this function does not return, any
 * later implementations of hook_user('logout') will not run.
 *
 * @param $invoke_hook
 *   If TRUE, invoke hook_user_logout() and save a watchdog mesage indicating
 *   that the user has logged out.
 */
function cas_logout($invoke_hook = TRUE) {
  global $user;

  // Build the logout URL.
  cas_phpcas_init();
  if (isset($_GET['destination'])) {

    // Add destination override so that a destination can be specified on the
    // logout link, e.g., caslogout?desination=http://foo.bar.com/foobar. We do
    // not allow absolute URLs to be passed via $_GET, as this can be an attack
    // vector.
    $colonpos = strpos($_GET['destination'], ':');
    $absolute = $colonpos !== FALSE && !preg_match('![/?#]!', substr($_GET['destination'], 0, $colonpos));
    $destination = !$absolute ? $_GET['destination'] : variable_get('cas_logout_destination', '');
  }
  else {
    $destination = variable_get('cas_logout_destination', '');
  }

  //Make it an absolute url.  This will also convert <front> to the front page.
  if ($destination) {
    $destination_url = url($destination, array(
      'absolute' => TRUE,
    ));
    $options = array(
      'service' => $destination_url,
      'url' => $destination_url,
    );
  }
  else {
    $options = array();
  }

  // Mimic user_logout().
  if ($invoke_hook) {
    watchdog('user', 'Session closed for %name.', array(
      '%name' => $user->name,
    ));

    // Only variables can be passed by reference workaround.
    $null = NULL;
    user_module_invoke('logout', $null, $user);
  }

  // Load the anonymous user
  $user = drupal_anonymous_user();

  // phpCAS automatically calls session_destroy().
  phpCAS::logout($options);
}

/**
 * Implements hook_block().
 *
 * Provides CAS login block for anonymous users.
 */
function cas_block($op = 'list', $delta = 0, $edit = array()) {
  global $user;
  if ($op == 'list') {
    $blocks[0]['info'] = t('CAS login');
    return $blocks;
  }
  elseif ($op == 'view') {
    $block = array();
    switch ($delta) {
      case 0:

        // For usability's sake, avoid showing two login forms on one page.
        if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
          $block['subject'] = t('User login');
          $block['content'] = drupal_get_form('cas_login_block');
        }
        return $block;
    }
  }
}

/**
 * Login form for the CAS login block.
 */
function cas_login_block($form_state) {
  $form['cas.return_to'] = array(
    '#type' => 'hidden',
    '#value' => cas_login_destination(),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t(variable_get('cas_login_invite', CAS_LOGIN_INVITE_DEFAULT)),
  );
  $form['cas_login_redirection_message'] = array(
    '#value' => '<div class="form-item cas-login-redirection-message">' . t(variable_get('cas_login_redir_message', CAS_LOGIN_REDIR_MESSAGE)) . '</div>',
    '#weight' => -1,
  );
  if (module_exists('persistent_login') && variable_get('cas_allow_rememberme', 0)) {
    $form['remember'] = array(
      '#type' => 'checkbox',
      '#title' => t('Remember me'),
      '#default_value' => 0,
    );
  }
  return $form;
}

/**
 * Submit handler for cas_login_block().
 */
function cas_login_block_submit($form, &$form_state) {
  if (!empty($form_state['values']['remember'])) {
    $_SESSION['cas_remember'] = 1;
  }
  $form_state['redirect'] = url('cas', array(
    'query' => $form_state['values']['cas.return_to'],
    'absolute' => TRUE,
  ));
}

/**
 * Determine if we should automatically check if the user is authenticated.
 *
 * This implements part of the CAS gateway feature.
 * @see phpCAS::checkAuthentication()
 *
 * @return
 *   TRUE if we should query the CAS server to see if the user is already
 *   authenticated, FALSE otherwise.
 */
function _cas_allow_check_for_login() {
  if (!variable_get('cas_check_first', 0)) {

    // The user has disabled the feature.
    return FALSE;
  }

  // Check to see if we already have.
  if (!empty($_COOKIE['cas_login_checked'])) {
    return FALSE;
  }

  // Check to see if we've got a search bot.
  if (isset($_SERVER['HTTP_USER_AGENT'])) {
    $crawlers = array(
      'Google',
      'msnbot',
      'Rambler',
      'Yahoo',
      'AbachoBOT',
      'accoona',
      'AcoiRobot',
      'ASPSeek',
      'CrocCrawler',
      'Dumbot',
      'FAST-WebCrawler',
      'GeonaBot',
      'Gigabot',
      'Lycos',
      'MSRBOT',
      'Scooter',
      'AltaVista',
      'IDBot',
      'eStyle',
      'Scrubby',
      'gsa-crawler',
    );

    // Return on the first find.
    foreach ($crawlers as $c) {
      if (stripos($_SERVER['HTTP_USER_AGENT'], $c) !== FALSE) {
        return FALSE;
      }
    }
  }

  // Do not force login for XMLRPC, Cron, or Drush.
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'xmlrpc.php')) {
    return FALSE;
  }
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'cron.php')) {
    return FALSE;
  }
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'drush')) {
    return FALSE;
  }
  if (!empty($_SERVER['argv'][0]) && stristr($_SERVER['argv'][0], 'drush')) {
    return FALSE;
  }

  // Test against exclude pages.
  if ($pages = variable_get('cas_exclude', CAS_EXCLUDE)) {
    $path = drupal_get_path_alias($_GET['q']);
    if (drupal_match_path($path, $pages)) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Determine if we should require the user be authenticated.
 *
 * @return
 *   TRUE if we should require the user be authenticated, FALSE otherwise.
 */
function _cas_force_login() {

  // The 'cas' page is a shortcut to force authentication.
  if (arg(0) == 'cas') {
    return TRUE;
  }

  // Do not force login for XMLRPC, Cron, or Drush.
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'xmlrpc.php')) {
    return FALSE;
  }
  if (stristr($_SERVER['SCRIPT_FILENAME'], 'cron.php')) {
    return FALSE;
  }
  if (function_exists('drush_verify_cli') && drush_verify_cli()) {
    return FALSE;
  }

  // Excluded page do not need login.
  if ($pages = variable_get('cas_exclude', CAS_EXCLUDE)) {
    $path = drupal_get_path_alias($_GET['q']);
    if (drupal_match_path($path, $pages)) {
      return FALSE;
    }
  }

  // Set the default behavior.
  $force_login = variable_get('cas_access', 0);

  // If we match the speficied paths, reverse the behavior.
  if ($pages = variable_get('cas_pages', '')) {
    $path = drupal_get_path_alias($_GET['q']);
    if (drupal_match_path($path, $pages)) {
      $force_login = !$force_login;
    }
  }
  return $force_login;
}

/**
 * Implementation of hook_form_alter().
 *
 * Overrides specific from settings based on user policy.
 */
function cas_form_alter(&$form, &$form_state, $form_id) {

  //drupal_set_message($form_id.'<pre>'.print_r($form,1).'</pre>');
  switch ($form_id) {
    case 'user_login':
    case 'user_login_block':
      if (variable_get('cas_login_form', CAS_NO_LINK) != CAS_NO_LINK) {
        drupal_add_css(drupal_get_path('module', 'cas') . '/cas.css', 'module');
        drupal_add_js(drupal_get_path('module', 'cas') . '/cas.js');
        if (!empty($form_state['post']['cas_identifier'])) {
          $form['name']['#required'] = FALSE;
          $form['pass']['#required'] = FALSE;
          unset($form['#submit']);
          $form['#validate'] = array(
            'cas_login_validate',
          );
        }
        $items = array();
        $items[] = array(
          'data' => l(t(variable_get('cas_login_invite', CAS_LOGIN_INVITE_DEFAULT)), '', array(
            'fragment' => ' ',
            'external' => TRUE,
          )),
          'class' => 'cas-link',
        );
        $items[] = array(
          'data' => l(t(variable_get('cas_login_drupal_invite', CAS_LOGIN_DRUPAL_INVITE_DEFAULT)), '', array(
            'fragment' => ' ',
            'external' => TRUE,
          )),
          'class' => 'uncas-link',
        );
        $form['cas_links'] = array(
          '#value' => theme('item_list', $items),
          '#weight' => 1,
        );
        $form['links']['#weight'] = 2;
        $form['cas_login_redirection_message'] = array(
          '#value' => '<div class="form-item cas-login-redirection-message">' . t(variable_get('cas_login_redir_message', CAS_LOGIN_REDIR_MESSAGE)) . '</div>',
          '#weight' => -1,
        );
        $form['cas_identifier'] = array(
          '#type' => 'checkbox',
          '#title' => t(variable_get('cas_login_invite', CAS_LOGIN_INVITE_DEFAULT)),
          '#default_value' => variable_get('cas_login_form', CAS_NO_LINK) == CAS_MAKE_DEFAULT,
          '#weight' => -1,
          '#description' => t(variable_get('cas_login_redir_message', CAS_LOGIN_REDIR_MESSAGE)),
        );
        $form['cas.return_to'] = array(
          '#type' => 'hidden',
          '#value' => cas_login_destination(),
        );
      }
      break;
    case 'user_profile_form':

      // We alter the 'account' tab only, not any other tab provided by the
      // profile module.
      if ($form['_category']['#value'] == 'account') {
        $account = user_load($form['#uid']);
        if (user_access('administer users')) {

          // The user is an administrator, so add fields to allow changing the
          // CAS username(s) associated with the account.
          $cas_names = $account->cas_names;
          $aids = array_keys($cas_names);
          $element = array(
            '#type' => 'textfield',
            '#title' => t('CAS username'),
            '#default_value' => array_shift($cas_names),
            '#cas_user_aid' => array_shift($aids),
            '#description' => t('<a href="@url">Create, edit or delete</a> additional CAS usernames associated with this account.', array(
              '@url' => url('user/' . $account->uid . '/cas'),
            )),
            '#element_validate' => array(
              '_cas_name_element_validate',
            ),
          );

          // See if any additional CAS usernames exist.
          if (!empty($cas_names)) {
            $element['#description'] .= ' <br />' . t('Other CAS usernames: %cas_names.', array(
              '%cas_names' => implode(', ', $cas_names),
            ));
          }

          // Position the element directly below the 'name' field.
          $position = array_search('name', array_keys($form['account'])) + 1;
          _cas_array_insert($form['account'], $position, array(
            'cas_name' => $element,
          ));
        }
        elseif (cas_is_external_user($account)) {

          // The user is not an administrator, so selectively remove the e-mail
          // and password fields.
          if (variable_get('cas_hide_email', 0)) {

            // We cannot just set #access to FALSE, as the form throws an error if the current
            // value would not pass validation.
            $form['account']['mail']['#type'] = 'hidden';
            $form['account']['mail']['#value'] = $form['account']['mail']['#default_value'];
            if (!$form['account']['mail']['#default_value']) {
              $form['account']['mail']['#value'] = $form['_account']['#value']->name . '@' . variable_get('cas_domain', '');
            }
          }
          if (variable_get('cas_hide_password', 0)) {
            $form['account']['pass']['#access'] = FALSE;
          }
        }
      }
      break;
    case 'user_pass':
      if (!user_access('administer users') && variable_get('cas_changePasswordURL', '') != '') {
        drupal_goto(variable_get('cas_changePasswordURL', ''));
      }
      break;
    case 'user_register':
      if (user_access('administer users')) {
        $element = array(
          '#type' => 'textfield',
          '#title' => t('CAS username'),
          '#default_value' => '',
          '#description' => t('If necessary, additional CAS usernames can be added after the account is created.'),
          '#element_validate' => array(
            '_cas_name_element_validate',
          ),
        );

        // Position the element directly below the 'name' field.
        $position = array_search('name', array_keys($form)) + 1;
        _cas_array_insert($form, $position, array(
          'cas_name' => $element,
        ));
      }
      elseif (variable_get('cas_registerURL', '') != '') {
        drupal_goto(variable_get('cas_registerURL', ''));
      }
      break;
    case 'user_admin_account':
      foreach (array_keys($form['name']) as $uid) {
        $result = db_query('SELECT cas_name FROM {cas_user} WHERE uid = %d', $uid);
        $cas_names = array();
        while ($record = db_fetch_object($result)) {
          $cas_names[] = $record->cas_name;
        }
        if ($cas_names) {
          $form['name'][$uid]['#value'] .= ' ' . check_plain('(' . implode(', ', $cas_names) . ')');
        }
      }
      break;
  }
}

/**
 * Form element 'cas_name' validator.
 *
 * If the element is disaplying an existing {cas_user} entry, set
 * #cas_user_aid to the corresponing authmap id to avoid spurious
 * validation errors.
 */
function _cas_name_element_validate($element, &$form_state) {
  if (empty($element['#value'])) {

    // Nothing to validate if the name is empty.
    return;
  }

  // If set, we ignore entries with a specified authmap id. This is used on
  // the user/%user/edit page to not throw validation errors when we do not
  // change the CAS username.
  if (isset($element['#cas_user_aid'])) {
    $uid = db_result(db_query("SELECT uid FROM {cas_user} WHERE cas_name = '%s' AND aid <> %d", $element['#value'], $element['#cas_user_aid']));
  }
  else {
    $uid = db_result(db_query("SELECT uid FROM {cas_user} WHERE cas_name = '%s'", $element['#value']));
  }
  if ($uid !== FALSE) {

    // Another user is using this CAS username.
    form_set_error('cas_name', t('The CAS username is <a href="@edit-user-url">already in use</a> on this site.', array(
      '@edit-user-url' => url('user/' . $uid . '/edit'),
    )));
  }
}

/**
 * Login form _validate hook
 */
function cas_login_validate($form, &$form_state) {
  if (!empty($form_state['values']['persistent_login'])) {
    $_SESSION['cas_remember'] = 1;
  }

  // Force redirection.
  unset($_REQUEST['destination']);
  drupal_goto('cas', $form_state['values']['cas.return_to']);
}

/**
 * Implements hook_token_list().
 */
function cas_token_list($type = 'all') {
  module_load_include('tokens.inc', 'cas');
  return _cas_token_list($type);
}

/**
 * Implements hook_token_values().
 */
function cas_token_values($type, $object = NULL) {
  module_load_include('tokens.inc', 'cas');
  return _cas_token_values($type, $object);
}

/*
 * CAS Sigle Sign Out - BEGIN
 */
function _cas_single_sign_out_check() {
  if (isset($_POST["logoutRequest"])) {
    $cas_logout_request_xml_string = utf8_encode(urldecode($_POST["logoutRequest"]));
    $cas_logout_request_xml = new SimpleXMLElement($cas_logout_request_xml_string);
    if (is_object($cas_logout_request_xml)) {
      $namespaces = $cas_logout_request_xml
        ->getNameSpaces();
      $xsearch = 'SessionIndex';
      if (isset($namespaces['samlp'])) {
        $cas_logout_request_xml
          ->registerXPathNamespace('samlp', $namespaces['samlp']);
        $xsearch = 'samlp:SessionIndex';
      }
      $cas_session_indexes = $cas_logout_request_xml
        ->xpath($xsearch);
      if ($cas_session_indexes) {
        $cas_session_index = (string) $cas_session_indexes[0];

        // Log them out now.
        // first lets find out who we want to log off
        $record = db_fetch_object(db_query_range("SELECT cld.uid, u.name FROM {users} u JOIN {cas_login_data} cld ON u.uid = cld.uid WHERE cld.cas_session_id = '%s'", $cas_session_index, 0, 1));
        if ($record) {
          watchdog('user', 'Session closed for %name by CAS logout request.', array(
            '%name' => $record->name,
          ));

          // remove all entry for user id in cas_login_data
          db_query("DELETE FROM {cas_login_data} WHERE uid = %d", $record->uid);

          // remove their session
          db_query("DELETE FROM {sessions} WHERE uid = %d", $record->uid);
        }
      }
    }

    // This request is done, so just exit.
    exit;
  }
}

/**
 * Return the current CAS username.
 */
function cas_current_user() {
  return isset($_SESSION['cas_name']) ? $_SESSION['cas_name'] : FALSE;
}

/**
 * Determine whether the specified user is an "external" CAS user.
 * When settings are set to use drupal as the user repository, then this
 * function will always return true.
 *
 * @param $account
 *   The user object for the user to query. If omitted, the current user is
 *   used.
 *
 * @return
 *   TRUE if the user is logged in via CAS.
 */
function cas_is_external_user($account = NULL) {
  if (!isset($account)) {
    $account = $GLOBALS['user'];
  }
  return in_array(cas_current_user(), $account->cas_names);
}
function _cas_single_sign_out_save_token($user) {

  // Ok lets save the CAS service ticket to DB so
  // we can handle CAS logoutRequests when they come
  if ($user->uid && $user->uid > 0 && !empty($_SESSION['cas_ticket'])) {
    db_query("INSERT INTO {cas_login_data} (cas_session_id, uid) VALUES ('%s', %d)", $_SESSION['cas_ticket'], $user->uid);
    unset($_SESSION['cas_ticket']);
  }
}

/**
 * Make sure that we persist ticket because of redirects performed by CAS.
 */
function _cas_single_sign_out_save_ticket() {
  if (isset($_GET['ticket'])) {
    $_SESSION['cas_ticket'] = $_GET['ticket'];
  }
}

/**
 * Determine whether a CAS user is blocked.
 *
 * @param $cas_name
 *   The CAS username.
 *
 * @return
 *   Boolean TRUE if the user is blocked, FALSE if the user is active.
 */
function _cas_external_user_is_blocked($cas_name) {
  $deny = db_fetch_object(db_query("SELECT u.name FROM {users} u JOIN {cas_user} c ON u.uid=c.uid WHERE u.status = 0 AND c.cas_name = '%s'", $cas_name));
  return $deny;
}

/**
 * Invokes hook_cas_user_TYPE() in every module.
 *
 * We cannot use module_invoke() because the arguments need to be passed by
 * reference.
 */
function cas_user_module_invoke($type, &$edit, $account) {
  foreach (module_implements('cas_user_' . $type) as $module) {
    $function = $module . '_cas_user_' . $type;
    $function($edit, $account);
  }
}

/**
 * Roles which should be granted to all CAS users.
 *
 * @return
 *   An associative array with the role id as the key and the role name as value.
 */
function cas_roles() {
  static $cas_roles;
  if (!isset($cas_roles)) {
    $cas_roles = array_intersect_key(user_roles(), array_filter(variable_get('cas_auto_assigned_role', array(
      DRUPAL_AUTHENTICATED_RID => TRUE,
    ))));
  }
  return $cas_roles;
}

/**
 * Register a CAS user with some default values.
 *
 * @param $cas_name
 *   The name of the CAS user.
 * @param $options
 *   An associative array of options, with the following elements:
 *    - 'edit': An array of fields and values for the new user. If omitted,
 *      reasonable defaults are used.
 *    - 'invoke_cas_user_presave': Defaults to FALSE. Whether or not to invoke
 *      hook_cas_user_presave() on the newly created account.
 *
 * @return
 *   The user object of the created user, or FALSE if the user cannot be
 *   created.
 */
function cas_user_register($cas_name, $options = array()) {

  // Add some reasonable defaults if they have not yet been provided.
  $edit = isset($options['edit']) ? $options['edit'] : array();
  $edit += array(
    'name' => $cas_name,
    'pass' => user_password(),
    'init' => $cas_name,
    'mail' => variable_get('cas_domain', '') ? $cas_name . '@' . variable_get('cas_domain', '') : '',
    'status' => 1,
    'roles' => array(),
  );
  $edit['roles'] += cas_roles();
  $edit['cas_name'] = $cas_name;

  // See if the user name is already taken.
  if (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE LOWER(name) = LOWER('%s')", $edit['name'])) > 0) {
    return FALSE;
  }

  // Create the user account.
  $account = user_save(drupal_anonymous_user(), $edit);
  watchdog("user", 'new user: %n (CAS)', array(
    '%n' => $account->name,
  ), WATCHDOG_NOTICE, l(t("edit user"), "admin/user/edit/{$account->uid}"));
  if (!empty($options['invoke_cas_user_presave'])) {

    // Populate $edit with some basic properties.
    $edit = array(
      'cas_user' => array(
        'name' => $cas_name,
      ),
    );

    // Allow other modules to make their own custom changes.
    cas_user_module_invoke('presave', $edit, $account);

    // Clean up extra variables before saving.
    unset($edit['cas_user']);
    $account = user_save($account, $edit);
  }
  return $account;
}

/**
 * Get the CAS attributes of the current CAS user.
 *
 * Ensures that phpCAS is properly initialized before getting the attributes.
 * @see phpCAS::getAttributes()
 *
 * @param $cas_name
 *   If provided, ensure that the currently logged in CAS user matches this
 *   CAS username.
 *
 * @return
 *   An associative array of CAS attributes.
 */
function cas_phpcas_attributes($cas_name = NULL) {
  if (isset($cas_name) && $cas_name != cas_current_user()) {

    // Attributes cannot be extracted for other users, since they are
    // stored in the session variable.
    return array();
  }
  cas_phpcas_init();
  if (phpCAS::isAuthenticated()) {
    if (method_exists('phpCAS', 'getAttributes')) {
      return phpCAS::getAttributes();
    }
  }
  return array();
}

/**
 * Insert an array into the specified position of another array.
 *
 * Preserves keys in associative arrays.
 * @see http://www.php.net/manual/en/function.array-splice.php#56794
 */
function _cas_array_insert(&$array, $position, $insert_array) {
  $first_array = array_splice($array, 0, $position);
  $array = array_merge($first_array, $insert_array, $array);
}

/**
 * Implements hook_views_api().
 */
function cas_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'cas') . '/includes/views',
  );
}

Functions

Namesort descending Description
cas_block Implements hook_block().
cas_current_user Return the current CAS username.
cas_form_alter Implementation of hook_form_alter().
cas_help Implementation of hook_help().
cas_init Implementation of hook_init().
cas_is_external_user Determine whether the specified user is an "external" CAS user. When settings are set to use drupal as the user repository, then this function will always return true.
cas_login_block Login form for the CAS login block.
cas_login_block_submit Submit handler for cas_login_block().
cas_login_check Checks to see if the user needs to be logged in.
cas_login_destination Helper function to rewrite the destination to avoid redirecting to login page after login.
cas_login_page Redirects to appropriate page based on user settings.
cas_login_validate Login form _validate hook
cas_logout Logs a user out of Drupal and then out of CAS.
cas_menu Implementation of hook_menu().
cas_menu_link_alter Implements hook_menu_link_alter().
cas_perm Implementation of hook_perm().
cas_phpcas_attributes Get the CAS attributes of the current CAS user.
cas_phpcas_init Initialize phpCAS.
cas_phpcas_load Loads the phpCAS library.
cas_roles Roles which should be granted to all CAS users.
cas_token_list Implements hook_token_list().
cas_token_values Implements hook_token_values().
cas_translated_menu_link_alter Implements hook_translated_menu_item_alter().
cas_user Implementation of hook_user().
cas_user_is_logged_in
cas_user_load_by_name Fetch a user object by CAS name.
cas_user_module_invoke Invokes hook_cas_user_TYPE() in every module.
cas_user_operations Implements hook_user_operations().
cas_user_operations_create_username Callback function to assign a CAS username to the account.
cas_user_operations_remove_usernames Callback function to remove CAS usernames from the account.
cas_user_register Register a CAS user with some default values.
cas_views_api Implements hook_views_api().
_cas_allow_check_for_login Determine if we should automatically check if the user is authenticated.
_cas_array_insert Insert an array into the specified position of another array.
_cas_external_user_is_blocked Determine whether a CAS user is blocked.
_cas_force_login Determine if we should require the user be authenticated.
_cas_name_element_validate Form element 'cas_name' validator.
_cas_single_sign_out_check
_cas_single_sign_out_save_ticket Make sure that we persist ticket because of redirects performed by CAS.
_cas_single_sign_out_save_token

Constants

Namesort descending Description
CAS_ADD_LINK
CAS_EXCLUDE
CAS_LOGIN_DRUPAL_INVITE_DEFAULT
CAS_LOGIN_INVITE_DEFAULT
CAS_LOGIN_REDIR_MESSAGE
CAS_MAKE_DEFAULT
CAS_NO_LINK @file Enables users to authenticate via a Central Authentication Service (CAS) Cas will currently work if the auto registration is turned on and will create user accounts automatically.