You are here

cas_server.module in CAS 6.3

Provides a protocol compliant version of CAS server 2.x

File

cas_server.module
View source
<?php

/**
 * @file Provides a protocol compliant version of CAS server 2.x
 */
define('CAS_LOGIN_COOKIE', 'cas_server_login');

/**
 * Implementation of hook_menu
 */
function cas_server_menu() {
  $items = array();
  $items['cas/login'] = array(
    'page callback' => 'cas_server_login',
    'title' => 'CAS Login',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['cas/validate'] = array(
    'page callback' => 'cas_server_validate',
    'title' => 'CAS Validate',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['cas/proxyValidate'] = array(
    'page callback' => 'cas_server_service_validate',
    'title' => 'CAS Proxy Service Validate',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['cas/serviceValidate'] = array(
    'page callback' => 'cas_server_service_validate',
    'title' => 'CAS Service Validate',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['cas/logout'] = array(
    'page callback' => 'cas_server_logout',
    'title' => 'CAS Logout',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_theme().
 */
function cas_server_theme() {
  return array(
    'cas_service_validate_success' => array(
      'arguments' => array(
        'name' => NULL,
        'attributes' => array(),
        'style' => 'jasig',
      ),
      'file' => 'cas_server.response.inc',
    ),
    'cas_service_validate_attributes' => array(
      'arguments' => array(
        'attributes' => array(),
        'style' => 'jasig',
      ),
      'file' => 'cas_server.response.inc',
    ),
    'cas_service_validate_failure' => array(
      'arguments' => array(
        'ticket' => NULL,
        'error_code' => NULL,
      ),
      'file' => 'cas_server.response.inc',
    ),
    'cas_service_logout_request' => array(
      'arguments' => array(
        'ticket' => NULL,
        'date' => NULL,
        'id' => NULL,
      ),
      'file' => 'cas_server.response.inc',
    ),
  );
}

/**
 * Implements hook_cas_server_user_attributes().
 *
 * Returns the user's roles.
 */
function cas_server_cas_server_user_attributes($account, $service) {
  return array(
    'uid' => $account->uid,
    'mail' => $account->mail,
    'created' => $account->created,
    'timezone' => $account->timezone,
    'language' => $account->language,
    'drupal_roles' => $account->roles,
  );
}
function cas_server_service_return() {
  global $user;
  $service = isset($_COOKIE[CAS_LOGIN_COOKIE]) ? $_COOKIE[CAS_LOGIN_COOKIE] : '';
  if ($service && $user->uid) {
    $ticket = _cas_server_save_ticket($user->uid, $service);
    setcookie(CAS_LOGIN_COOKIE, "", -3600);
    drupal_goto($service, 'ticket=' . urlencode($ticket));
  }
}

/**
 * Handle login
 *
 */
function cas_server_login() {

  // Set login cookie so that we know we're in the process of logging in
  global $user;
  $output = '';
  $service = isset($_REQUEST['service']) ? $_REQUEST['service'] : '';
  $gateway = isset($_REQUEST['gateway']);
  if ($user->uid) {
    if ($service) {
      $_COOKIE[CAS_LOGIN_COOKIE] = $service;
    }
    $output = t('You have successfully logged into CAS');
    cas_server_service_return();
  }
  else {
    if ($gateway && $service) {
      drupal_goto($service);
    }
    else {

      // Redirect to user login
      if ($service) {
        setcookie(CAS_LOGIN_COOKIE, $service);
      }
      $output .= l(t('Login'), 'user', array(
        'query' => 'destination=cas/login',
      ));
      drupal_goto('user', 'destination=cas/login');
    }
  }
  return $output;
}

/**
 * Validate the ticket using a CAS 1.x methodology
 * This provides the simple non-xml based
 */
function cas_server_validate() {

  // Prevent this page from being cached.
  global $conf;
  $conf['cache'] = FALSE;

  // Set content type.
  drupal_set_header('Content-Type: text/plain; charset=utf-8');

  //Obtain the ticket from the url and validate it.
  $ticket = isset($_REQUEST['ticket']) ? $_REQUEST['ticket'] : '';
  $service = isset($_REQUEST['service']) ? $_REQUEST['service'] : '';
  $user_name = _cas_server_validate($service, $ticket);
  if ($user_name) {
    print "yes\n";
    print "{$user_name}\n";
  }
  else {
    print "no\n";
    print "\n";
  }
}

/**
 * serviceValidate method using cas 2.0
 * Returns data in xml
 */
function cas_server_service_validate() {

  // Prevent this page from being cached.
  global $conf;
  $conf['cache'] = FALSE;

  // Set content type.
  drupal_set_header('Content-Type: text/xml; charset=utf-8');
  $ticket = isset($_REQUEST['ticket']) ? $_REQUEST['ticket'] : '';
  $service = isset($_REQUEST['service']) ? $_REQUEST['service'] : '';
  $user_name = _cas_server_validate($service, $ticket);
  if (!$user_name) {
    $cas_error = 'INVALID_TICKET';
  }
  if (!$ticket || !$service) {
    $cas_error = 'INVALID_REQUEST';
  }
  if ($user_name) {

    //@TODO Generate proxy granting ticket
    $account = user_load(array(
      'name' => $user_name,
    ));

    // Generate a list of attributes to return.
    $attributes = module_invoke_all('cas_server_user_attributes', $account, $service, $ticket);

    // Let other modules alter the list of attributes.
    $context = array(
      'service' => $service,
      'ticket' => $ticket,
    );
    drupal_alter('cas_server_user_attributes', $attributes, $account, $context);
    print theme('cas_service_validate_success', $user_name, $attributes);
    watchdog('cas', 'User %name CAS sucessully authenticated.', array(
      '%name' => $user_name,
    ));
  }
  else {
    print theme('cas_service_validate_failure', $ticket, $cas_error);
    watchdog('cas', 'Ticket %ticket for service %service not recognized.', array(
      '%ticket' => $ticket,
      '%service' => $service,
    ));
  }
}

/**
 * Test to see if a one time use ticket is valid
 *
 * @param unknown_type $ticket
 * @return unknown
 */
function _cas_server_validate($service, $ticket) {

  // Look up the ticket
  $user_name = '';
  $ticket_info = array(
    $service,
    $ticket,
  );
  $result = db_query_range("SELECT u.name FROM {cas_server_tickets} t JOIN {users} u ON t.uid=u.uid  WHERE t.service = '%s' and t.ticket = '%s' AND valid=1", $ticket_info, 0, 1);
  if ($result !== FALSE) {
    while ($ticket_data = db_fetch_object($result)) {
      $user_name = $ticket_data->name;
    }
  }
  db_query("UPDATE {cas_server_tickets} SET valid=0 WHERE ticket='%s'", array(
    $ticket,
  ));
  return $user_name;
}

/**
 * Generate a one time use login ticket for the user in question.
 *
 * @param int $uid
 */
function _cas_server_save_ticket($uid, $service) {

  // Generate the ticket
  $time = time();
  $ticket = 'ST-' . user_password();
  $valid = 1;
  $ticket_data = array(
    $uid,
    $service,
    $ticket,
    $time,
    $valid,
  );

  // Save the ticket to the db
  if ($uid && $service) {
    db_query("INSERT INTO {cas_server_tickets} (uid, service, ticket, timestamp, valid) VALUES (%d, '%s', '%s', %d, %d)", $ticket_data);
  }
  return $ticket;
}

/**
 * Menu callback; triggers a CAS logout.
 */
function cas_server_logout() {
  global $user;
  watchdog('user', 'Session closed for %name.', array(
    '%name' => $user->name,
  ));

  // Destroy the current session:
  session_destroy();

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

  // Load the anonymous user
  $user = drupal_anonymous_user();
  $output = '<p>' . t('You have been logged out successfully.') . '</p>';
  if (isset($_REQUEST['url'])) {
    $output .= '<p>' . l(t('Continue'), $_REQUEST['url']) . '</p>';
  }
  return $output;
}

/**
 * Send CAS a logout requests for each of the user's CAS tickets.
 *
 * @param $account
 *   The user for whom to send CAS logout requests.
 */
function cas_server_logout_clients($account) {
  $result = db_query("SELECT service, ticket, valid FROM {cas_server_tickets} WHERE uid=%d", array(
    $account->uid,
  ));
  $expired_tickets = array();
  while ($client = db_fetch_object($result)) {
    $expired_tickets[] = $client->ticket;
    if (!$client->valid) {
      $id = 'LR-' . user_password();
      $date = date('c');
      $logout_request = theme('cas_service_logout_request', $client->ticket, $date, $id);

      // Send POST request
      $response = drupal_http_request($client->service, array(
        'Content-Type' => 'application/x-www-form-urlencoded',
      ), 'POST', 'logoutRequest=' . urlencode($logout_request));
      if (@$response->error) {
        watchdog('error', 'Error in CAS logout Request - %code : %message', array(
          '%code' => $response->code,
          '%error' => $response->error,
        ));
      }
    }
  }
  if ($expired_tickets) {
    db_query("DELETE FROM {cas_server_tickets} WHERE ticket IN (" . db_placeholders($expired_tickets, 'int') . ")", $expired_tickets);
  }
}

/**
 * Implements hook_user().
 */
function cas_server_user($op, &$edit, &$account, $category = NULL) {
  if ($op == 'logout') {
    cas_server_logout_clients($account);
  }
}

Functions

Namesort descending Description
cas_server_cas_server_user_attributes Implements hook_cas_server_user_attributes().
cas_server_login Handle login
cas_server_logout Menu callback; triggers a CAS logout.
cas_server_logout_clients Send CAS a logout requests for each of the user's CAS tickets.
cas_server_menu Implementation of hook_menu
cas_server_service_return
cas_server_service_validate serviceValidate method using cas 2.0 Returns data in xml
cas_server_theme Implements hook_theme().
cas_server_user Implements hook_user().
cas_server_validate Validate the ticket using a CAS 1.x methodology This provides the simple non-xml based
_cas_server_save_ticket Generate a one time use login ticket for the user in question.
_cas_server_validate Test to see if a one time use ticket is valid

Constants

Namesort descending Description
CAS_LOGIN_COOKIE @file Provides a protocol compliant version of CAS server 2.x