From 26b94ad3093fd11648bd310768d4891c0b5d7e14 Mon Sep 17 00:00:00 2001
From: Bradley M. Froehle <brad.froehle@gmail.com>
Date: Mon, 18 Apr 2011 12:41:09 -0700
Subject: [PATCH] Upgrade test routines to prepare for CAS attribute support.

- Use phpCAS 1.2.1 (with patches).
- Use theme functions for service responses.
---
 cas.test                    |   36 ++++++++++-------
 tests/cas_test.module       |   79 ++++++++++++++++++++++----------------
 tests/cas_test.response.inc |   90 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 158 insertions(+), 47 deletions(-)
 create mode 100644 tests/cas_test.response.inc

diff --git a/cas.test b/cas.test
index 9c32cbe..05b4d4b 100644
--- a/cas.test
+++ b/cas.test
@@ -25,7 +25,7 @@ class CasTestHelper extends DrupalWebTestCase {
     $this->admin_user = $this->drupalCreateUser(array('administer users', 'administer cas'));
 
     // Download and extract in PHPCAS.
-    $this->downloadExtractPhpCas('1.1.3');
+    $this->downloadExtractPhpCas('1.2.1');
   }
 
   /**
@@ -67,10 +67,15 @@ class CasTestHelper extends DrupalWebTestCase {
       // Extract the files.
       $archiver = archiver_get_archiver($local_archive);
       $archiver->extract($simpletest_cache);
-      if ($version == '1.1.3') {
-        // Work around issue https://issues.jasig.org/browse/PHPCAS-105.
-        // @see http://drupal.org/node/1120034
-        system_retrieve_file('http://drupal.org/files/issues/CAS-1.1.3-CAS-client.txt', $cas_library_dir . '/CAS/client.php', FALSE, FILE_EXISTS_REPLACE);
+      // Work around issue https://issues.jasig.org/browse/PHPCAS-105.
+      // @see http://drupal.org/node/1120034
+      switch ($version) {
+        case '1.1.3':
+          system_retrieve_file('http://drupal.org/files/issues/CAS-1.1.3-CAS-client.txt', $cas_library_dir . '/CAS/client.php', FALSE, FILE_EXISTS_REPLACE);
+          break;
+        case '1.2.1':
+          system_retrieve_file('http://drupal.org/files/issues/CAS-1.2.1-CAS-client.txt', $cas_library_dir . '/CAS/client.php', FALSE, FILE_EXISTS_REPLACE);
+          break;
       }
     }
     if (function_exists('sem_get')) {
@@ -121,15 +126,18 @@ class CasTestHelper extends DrupalWebTestCase {
    * @param $account
    *   A user object with a valid CAS username field, or the CAS username as a
    *   string.
+   * @param $attributes
+   *   Additional attributes for the CAS user.
    */
-  function casLogin($account) {
+  function casLogin($account, $attributes = array()) {
     if ($this->loggedInUser) {
       $this->drupalLogout();
     }
 
     // Log in the user.
     $cas_name = is_object($account) ? $account->cas_name : $account;
-    variable_set('cas_test_cas_name', $cas_name);
+    $cas_user = array('name' => $cas_name, 'attributes' => $attributes);
+    variable_set('cas_test_cas_user', $cas_user);
     $this->drupalGet('cas');
 
     $pass = $this->assertLink(t('Log out'), 0, t('CAS user %cas_name successfully logged in.', array('%cas_name' => $cas_name)), t('User login'));
@@ -302,7 +310,7 @@ class CasUserTestCase extends CasTestHelper {
    */
   function testCasAutoRegister() {
     $cas_name = $this->randomName();
-    variable_set('cas_test_cas_name', $cas_name);
+    variable_set('cas_test_cas_user', array('name' => $cas_name));
 
     // Test that the user is not automatically registered.
     variable_set('cas_user_register', FALSE);
@@ -319,7 +327,7 @@ class CasUserTestCase extends CasTestHelper {
     // Create a new user.
     $user2 = $this->drupalCreateUser();
     $cas_name = $user2->name;
-    variable_set('cas_test_cas_name', $cas_name);
+    variable_set('cas_test_cas_user', array('name' => $cas_name));
 
     // Test that CAS does not hijack an existing username.
     $this->drupalGet('cas');
@@ -452,7 +460,7 @@ class CasLoginRedirectionTestCase extends CasTestHelper {
     // Create a CAS user.
     $account = $this->casCreateUser();
     $cas_name = $account->cas_name;
-    variable_set('cas_test_cas_name', $cas_name);
+    variable_set('cas_test_cas_user', array('name' => $cas_name));
 
     // Test going to 'cas'
     $this->drupalGet('cas');
@@ -600,7 +608,7 @@ class CasGatewayTestCase extends CasTestHelper {
     // Create a user.
     $cas_name = $this->randomName();
     $account = $this->casCreateUser($cas_name);
-    variable_set('cas_test_cas_name', $cas_name);
+    variable_set('cas_test_cas_user', array('name' => $cas_name));
 
     $this->drupalGet('node');
     $this->assertLoggedIn($account);
@@ -627,7 +635,7 @@ class CasRequiredLoginTestCase extends CasTestHelper {
     $node1 = $this->drupalCreateNode();
     $node2 = $this->drupalCreateNode();
     $account = $this->casCreateUser();
-    variable_set('cas_test_cas_name', $account->cas_name);
+    variable_set('cas_test_cas_user', array('name' => $account->cas_name));
 
     $this->drupalGet("node/$node2->nid");
 
@@ -649,7 +657,7 @@ class CasRequiredLoginTestCase extends CasTestHelper {
     $this->assertField('pass', t('Password field found.'), t('Logout'));
 
     // Verify that accessing any other page redirects to the login page.
-    variable_del('cas_test_cas_name');
+    variable_del('cas_test_cas_user');
     $this->drupalGet('node');
     $this->assertText('No CAS name provided.');
   }
@@ -660,7 +668,7 @@ class CasRequiredLoginTestCase extends CasTestHelper {
   function testCasExclude() {
     $node = $this->drupalCreateNode();
     $account = $this->casCreateUser();
-    variable_set('cas_test_cas_name', $account->cas_name);
+    variable_set('cas_test_cas_user', array('name' => $account->cas_name));
 
     variable_set('cas_check_first', TRUE);
     variable_set('cas_exclude', "node/$node->nid");
diff --git a/tests/cas_test.module b/tests/cas_test.module
index 079126f..cdfba80 100644
--- a/tests/cas_test.module
+++ b/tests/cas_test.module
@@ -6,6 +6,26 @@
  */
 
 /**
+ * Implements hook_theme().
+ */
+function cas_test_theme() {
+  return array(
+    'cas_service_validate_success' => array(
+      'variables' => array('name' => NULL, 'attributes' => NULL),
+      'file' => 'cas_test.response.inc',
+    ),
+    'cas_service_validate_attributes' => array(
+      'variables' => array('attributes' => NULL, 'style' => 'jasig'),
+      'file' => 'cas_test.response.inc',
+    ),
+    'cas_service_validate_failure' => array(
+      'variables' => array('ticket' => NULL, 'error_code' => NULL),
+      'file' => 'cas_test.response.inc',
+    ),
+  );
+}
+
+/**
  *
  */
 function cas_test_cas_phpcas_alter() {
@@ -59,47 +79,48 @@ function cas_test_menu() {
     'page callback' => 'cas_test_validate',
     'title' => 'CAS Validate',
     'access callback' => TRUE,
-    'delivery callback' => '',
     'type' => MENU_CALLBACK,
   );
   $items['cas_test/serviceValidate'] = array(
     'page callback' => 'cas_test_service_validate',
     'title' => 'CAS Service Validate',
     'access callback' => TRUE,
-    'delivery callback' => '',
     'type' => MENU_CALLBACK,
   );
   $items['cas_test/proxyValidate'] = array(
     'page callback' => 'cas_test_service_validate',
     'title' => 'CAS Proxy Ticket Validate',
     'access callback' => TRUE,
-    'delivery callback' => '',
     'type' => MENU_CALLBACK,
   );
   $items['cas_test/logout'] = array(
     'page callback' => 'cas_test_logout',
     'title' => 'CAS Logout',
     'access callback' => TRUE,
-    'delivery callback' => '',
     'type' => MENU_CALLBACK,
-    );
+  );
   return $items;
 }
 
 /**
  * Initiate a login request.
  *
- * Set the 'cas_test_cas_name' variable to the CAS name of the user to be
- * logged in.
+ * Set the 'cas_test_cas_user' variable to an associative array containing:
+ * - 'name': CAS username.
+ * - 'attributes': (optional) Any other name-value pairs to be returned by the
+ *   CAS server.
  */
 function cas_test_login() {
   // Get the service and make a ticket.
   $service = $_REQUEST['service'];
-  $cas_name = variable_get('cas_test_cas_name', '');
+  $cas_user = variable_get('cas_test_cas_user', '');
 
-  if ($cas_name) {
+  if ($cas_user) {
+    if (!is_array($cas_user)) {
+      $cas_user = array('name' => $cas_user);
+    }
     // Generate a ticket and redirect to the service URL with the login ticket.
-    $ticket = _cas_test_ticket_generate($service, $cas_name);
+    $ticket = _cas_test_ticket_generate($service, $cas_user);
     // Force redirection.
     unset($_GET['destination']);
     drupal_goto($service, array('query' => array('ticket' => $ticket)));
@@ -124,8 +145,9 @@ function cas_test_validate() {
   //Obtain the ticket from the url and validate it.
   $ticket = $_GET['ticket'];
   $service = $_GET['service'];
-  $cas_name = _cas_test_ticket_validate($service, $ticket);
-  if ($cas_name) {
+  $cas_user = _cas_test_ticket_validate($service, $ticket);
+  if ($cas_user) {
+    $cas_name = $cas_user['name'];
     print "yes\n";
     print "$cas_name\n";
   }
@@ -142,25 +164,14 @@ function cas_test_validate() {
 function cas_test_service_validate() {
   $ticket = $_GET['ticket'];
   $service = $_GET['service'];
-  $cas_name = _cas_test_ticket_validate($service, $ticket);
-  if (!$cas_name ) $cas_error='INVALID_TICKET';
-  if (!$ticket || !$service) $cas_error='INVALID_REQUEST';
 
   header('Content-type:', 'text/xml');
-  if ($cas_name) {
-    //@TODO Generate proxy granting ticket
-    print "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n" .
-          "  <cas:authenticationSuccess>\n" .
-          "    <cas:user>$cas_name</cas:user>\n" .
-          "  </cas:authenticationSuccess>\n" .
-          "</cas:serviceResponse>\n";
+  if ($cas_user = _cas_test_ticket_validate($service, $ticket)) {
+    print theme('cas_service_validate_success', $cas_user);
   }
   else {
-    print "<cas:serviceReponse xmlns:cas='http://www.yale.edu/tp/cas'>\n" .
-          "  <cas:authenticationFailure code=\"$cas_error\">\n" .
-          "     Ticket $ticket not recognized.\n" .
-          "  </cas:authenticationFailure>" .
-          "</cas:serviceResponse>";
+    $cas_error = (!$ticket || !$service) ? 'INVALID_REQUEST' : 'INVALID_TICKET';
+    print theme('cas_service_validate_failure', array('ticket' => $ticket, 'error_code' => $error_code));
   }
   exit();
 }
@@ -183,25 +194,27 @@ function cas_test_logout() {
  *
  * @param $service
  *   The service URL.
- * @param $cas_name
- *   The CAS username.
+ * @param $cas_user
+ *   An associative array containing the following keys:
+ *     - 'name': The CAS username.
+ *     - 'attributes': Any other key-value pairs the CAS server should return.
  *
  * @return
  *   A login ticket which may be used to authenticate the CAS username at the
  *   service URL.
  */
-function _cas_test_ticket_generate($service, $cas_name) {
+function _cas_test_ticket_generate($service, $cas_user) {
   // Generate a one-time ticket.
   $ticket = 'ST-' . user_password(32);
 
   // Save the ticket in the database.
   $tickets = variable_get('cas_test_tickets', array());
-  $tickets[$service][$ticket] = $cas_name;
+  $tickets[$service][$ticket] = $cas_user;
   variable_set('cas_test_tickets', $tickets);
 
   // Save the name in the database for single sign-out.
   $sso = variable_get('cas_test_sso', array());
-  $sso[$cas_name][$service][] = $ticket;
+  $sso[$cas_user['name']][$service][] = $ticket;
   variable_set('cas_test_sso', $sso);
 
   return $ticket;
@@ -233,7 +246,7 @@ function _cas_test_ticket_validate($service, $ticket) {
 /**
  * Sign out the specified CAS user.
  *
- * @param $cas_name
+ * @param $cas_user
  */
 function cas_test_single_sign_out($cas_name) {
   $sso = variable_get('cas_test_sso', array());
diff --git a/tests/cas_test.response.inc b/tests/cas_test.response.inc
new file mode 100644
index 0000000..cfbee14
--- /dev/null
+++ b/tests/cas_test.response.inc
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Response callbacks for the cas_test module.
+ */
+
+/**
+ * Returns a CAS 2.0 service response for a validation success.
+ *
+ * @param $variables
+ *   An associative array containing the keys:
+ *   - 'name': CAS username.
+ *   - 'attributes': (optional) CAS attributes.
+ */
+function theme_cas_service_validate_success($variables) {
+  $cas_name = $variables['name'];
+  $output .= "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n";
+  $output .= "<cas:authenticationSuccess>\n";
+  $output .= "<cas:user>$cas_name</cas:user>\n";
+  $output .= theme('cas_service_validate_attributes', $variables);
+  $output .= "</cas:authenticationSuccess>\n";
+  $output .= "</cas:serviceResponse>\n";
+
+  return $output;
+}
+
+/**
+ * Returns CAS attributes as part of a CAS 2.0 service response.
+ *
+ * @param $variables
+ *   An asociative array containing the keys 'attributes' and 'style', where
+ *   the value of 'style' must be one of:
+ *     - 'jasig' (default)
+ *     - 'rubycas'
+ *     - 'name-value'
+ */
+function theme_cas_service_validate_attributes($variables) {
+  $attributes = $variables['attributes'];
+  $style = $variables['style'];
+
+  $output = '';
+  switch($style) {
+    case 'jasig':
+    default:
+      $output .= "<cas:attributes>\n";
+      $output .= "<cas:attraStyle>Jasig</cas:attraStyle>\n";
+      foreach ($attributes as $name => $values) {
+        foreach ((array) $values as $value) {
+          $output .= strtr("<cas:!name>!value</cas:!name>\n", array('!name' => $name, '!value' => $value));
+        }
+      }
+      $output .= "</cas:attributes>\n";
+    case 'rubycas':
+      $output .= "<cas:attraStyle>RubyCAS</cas:attraStyle>\n";
+      foreach ($attributes as $name => $values) {
+        foreach ((array) $values as $value) {
+          $output .= strtr("<cas:!name>!value</cas:!name>\n", array('!name' => $name, '!value' => $value));
+        }
+      }
+    case 'name-value':
+      $output .= "<cas:attribute name='attraStyle' value='Name-Value' />\n";
+      foreach ($attributes as $name => $values) {
+        foreach ((array) $values as $value) {
+          $output .= strtr("<cas:attribute name='!name' value='!value' />\n", array('!name' => $name, '!value' => $value));
+        }
+      }
+  }
+
+  return $output;
+}
+
+
+/**
+ * Returns a CAS 2.0 service response for a validation failure.
+ *
+ * @param $variables
+ *  An associative array containing the keys 'ticket' and 'error_code'.
+ */
+function theme_cas_service_validate_failure($variables) {
+  $ticket = $variables['ticket'];
+  $error_code = $variables['error_code'];
+  $output = "<cas:serviceReponse xmlns:cas='http://www.yale.edu/tp/cas'>\n";
+  $output .= "<cas:authenticationFailure code=\"$error_code\">\n";
+  $output .= "Ticket $ticket not recognized.\n";
+  $output .= "</cas:authenticationFailure>\n";
+  $output .= "</cas:serviceResponse>\n";
+
+  return $output;
+}
-- 
1.7.4.1

