From 235e302aec7d0a2e0232d629e4a0b5fe493d3495 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                    |   39 ++++++++++++--------
 tests/cas_test.module       |   79 +++++++++++++++++++++++-----------------
 tests/cas_test.response.inc |   85 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 155 insertions(+), 48 deletions(-)
 create mode 100644 tests/cas_test.response.inc

diff --git a/cas.test b/cas.test
index dd0d539..6bea350 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');
   }
 
   /**
@@ -74,11 +74,17 @@ class CasTestHelper extends DrupalWebTestCase {
       // Extract the files.
       $archiver = new Archive_Tar($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
-        $result = drupal_http_request('http://drupal.org/files/issues/CAS-1.1.3-CAS-client.txt');
-        file_put_contents($cas_library_dir . '/CAS/client.php', $result->data);
+      // Work around issue https://issues.jasig.org/browse/PHPCAS-105.
+      // @see http://drupal.org/node/1120034
+      switch ($version) {
+        case '1.1.3':
+          $result = drupal_http_request('http://drupal.org/files/issues/CAS-1.1.3-CAS-client.txt');
+          file_put_contents($cas_library_dir . '/CAS/client.php', $result->data);
+          break;
+        case '1.2.1':
+          $result = drupal_http_request('http://drupal.org/files/issues/CAS-1.2.1-CAS-client_0.txt');
+          file_put_contents($cas_library_dir . '/CAS/client.php', $result->data);
+          break;
       }
     }
 
@@ -130,15 +136,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'));
@@ -307,7 +316,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);
@@ -324,7 +333,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');
@@ -454,7 +463,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');
@@ -602,7 +611,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);
@@ -629,7 +638,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");
 
@@ -651,7 +660,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.');
   }
@@ -662,7 +671,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 e48efb0..d65e7ab 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(
+      'arguments' => array('name' => NULL, 'attributes' => array(), 'style' => 'jasig'),
+      'file' => 'cas_test.response.inc',
+    ),
+    'cas_service_validate_attributes' => array(
+      'arguments' => array('attributes' => array(), 'style' => 'jasig'),
+      'file' => 'cas_test.response.inc',
+    ),
+    'cas_service_validate_failure' => array(
+      'arguments' => array('ticket' => NULL, 'error_code' => NULL),
+      'file' => 'cas_test.response.inc',
+    ),
+  );
+}
+
+/**
  *
  */
 function cas_test_cas_phpcas_alter() {
@@ -58,47 +78,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($_REQUEST['destination']);
     drupal_goto($service, array('ticket' => $ticket));
@@ -123,8 +144,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";
   }
@@ -141,25 +163,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['name'], $cas_user['attributes']);
   }
   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', $ticket, $error_code);
   }
   exit();
 }
@@ -182,25 +193,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;
@@ -232,7 +245,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..bc17d34
--- /dev/null
+++ b/tests/cas_test.response.inc
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Response callbacks for the cas_test module.
+ */
+
+/**
+ * Returns a CAS 2.0 service response for a validation success.
+ *
+ * @param $name
+ *   CAS username.
+ * @param $attributes
+ *   If specified, the CAS attributes for the user.
+ */
+function theme_cas_service_validate_success($name, $attributes = array(), $style = 'jasig') {
+  $output .= "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n";
+  $output .= "<cas:authenticationSuccess>\n";
+  $output .= "<cas:user>$name</cas:user>\n";
+  $output .= theme('cas_service_validate_attributes', $attributes, $style);
+  $output .= "</cas:authenticationSuccess>\n";
+  $output .= "</cas:serviceResponse>\n";
+
+  return $output;
+}
+
+/**
+ * Returns CAS attributes as part of a CAS 2.0 service response.
+ *
+ * @param $attributes
+ *  The CAS attributes for the user
+ * @param $style
+ *   Must be one of:
+ *     - 'jasig' (default)
+ *     - 'rubycas'
+ *     - 'name-value'
+ */
+function theme_cas_service_validate_attributes($attributes = array(), $style = 'jasig') {
+  $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 $ticket
+ * @param $error_code
+ */
+function theme_cas_service_validate_failure($ticket, $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

