Fix for Security scanner simpletest integration

  1. <?php
  2.     function assert(&$expectation, $compare, $message = '%s') {
  3.         if ($expectation->test($compare)) {
  4.             return $this->pass(sprintf(
  5.                     $message,
  6.                     $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
  7.         } else {
  8.             return $this->fail(sprintf(
  9.                     $message,
  10.                     $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
  11.         }
  12.     }
  13.  
  14. Error:
  15. Fatal error: Call to a member function getDumper() on a non-object in E:\wamp\www\soc2008\sites\all\modules\simpletest\simpletest\test_case.php on line 312
  16.  
  17.  
  18. ?>
  19.  
  20. <?php
  21. // $Id: security_scanner.module,v 1.16 2008/07/26 09:42:29 ingo86 Exp $
  22.  
  23. // Including DrupalWebTestCase() class dependencies
  24. include_once drupal_get_path('module', 'simpletest') . '/simpletest/web_tester.php';
  25. include_once drupal_get_path('module', 'simpletest') . '/simpletest/unit_tester.php';
  26. include_once drupal_get_path('module', 'simpletest') . '/drupal_test_case.php';
  27. //include_once drupal_get_path('module', 'simpletest') . '/drupal_unit_tests.php';
  28. include_once drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php';
  29.  
  30.  
  31. // Make my own class that extends DrupalWebTestCase()
  32. // This has to be removed when this variable will switch to public into the main simpletest module.
  33. class DrupalSecurityScannerClass extends DrupalWebTestCase {
  34.   public $curl_options = array();
  35. }
  36.  
  37.   /**
  38.    * Takes a path and returns an absolute path.
  39.    *
  40.    * @param @path
  41.    *   The path, can be a Drupal path or a site-relative path. It might have a
  42.    *   query, too. Can even be an absolute path which is just passed through.
  43.    * @return
  44.    *   An absolute path.
  45.    */
  46.   function getAbsoluteUrl($path) {
  47.     $options = array('absolute' => TRUE);
  48.     $parts = parse_url($path);
  49.     // This is more crude than the menu_is_external but enough here.
  50.     if (empty($parts['host'])) {
  51.       $path = $parts['path'];
  52.       $base_path = base_path();
  53.       $n = strlen($base_path);
  54.       if (substr($path, 0, $n) == $base_path) {
  55.         $path = substr($path, $n);
  56.       }
  57.       if (isset($parts['query'])) {
  58.         $options['query'] = $parts['query'];
  59.       }
  60.       $path = url($path, $options);
  61.     }
  62.     return $path;
  63.   }
  64.  
  65.   /**
  66.    * Implementation of hook_menu().
  67.    */
  68.   function security_scanner_menu() {
  69.     $items['admin/settings/security_scanner'] = array(
  70.       'title' => 'Security Scanner',
  71.       'page callback' => 'page_security_scanner',
  72.       'access arguments' => array('access scanner'),
  73.       'type' => MENU_NORMAL_ITEM,
  74.     );
  75.   return $items;
  76.   }
  77.  
  78.   /**
  79.    *  Settings page for security scanner module
  80.    */
  81.   function page_security_scanner() {
  82.         return drupal_get_form('security_scanner_form');
  83.   }
  84.  
  85.   /**
  86.    *  Form page into security scanner
  87.    */
  88.   function security_scanner_form($form_state) {
  89.     $options = array('0' => t('Crawl'), '1' => t('Seed'), '2' => t('ReCrawl'));
  90.     $form['settings']['mode'] = array(
  91.       '#type' => 'radios',
  92.       '#title' => t('Mode'),
  93.       '#default_value' =>  variable_get('security_scanner_settings', 0),
  94.       '#options' => $options,
  95.       '#description' => t('Set the mode that the security scanner will works.'),
  96.     );
  97.     $form['submit'] = array(
  98.       '#type' => 'submit',
  99.       '#value' => 'Submit',
  100.     );
  101.     return $form;
  102.   }
  103.  
  104.   /**
  105.    * security_scanner_form_submit():
  106.    * Provide submit handler to settings form;
  107.    */
  108.   function security_scanner_form_submit($form, &$form_state) {
  109.     variable_set('security_scanner_settings', $form_state['values']['mode']);
  110.     drupal_set_message(t('Your settings has been saved.'));
  111.   }
  112.  
  113.   /**
  114.    *  Implementation of the crawler function.
  115.    *  Flow:
  116.    *  1- set crawler_id but leave status = 0
  117.    *  2- make status = 1
  118.    *  3- make status = 2            
  119.    */    
  120.   function crawler_framework($selection_query, $initial_status, $function_callback) {
  121.     // Initialize the crawler
  122.     db_query("INSERT INTO {crawler} VALUES (default)");
  123.     $crawler_id = db_last_insert_id('crawler', 'id');
  124.     $selection_query += array(
  125.       'join' => '',
  126.       'where' => '',
  127.       'parameters' => array(),
  128.     );
  129.     $fields = empty($selection_query['fields']) ? '' : ','. implode(', ', $selection_query['fields']);
  130.     $sql = 'SELECT l.path'. $fields .' FROM {crawler_links} AS l '. $selection_query['join'] .' WHERE crawler_id = %d and status = %d' . $selection_query['where'];
  131.     array_unshift($selection_query['parameters'], $crawler_id, $initial_status+1);
  132.     $time = time() + 40;
  133.     while (time() < $time) {
  134.       $status = $initial_status;
  135.       //Mark the extracted page as visited
  136.       $status++;
  137.       db_query("UPDATE {crawler_links} SET crawler_id = %d, status = %d WHERE status = %d LIMIT 1", $crawler_id, $status, $initial_status);
  138.       // Get the link from crawler_links table
  139.       $selected_results = db_fetch_array(db_query_range($sql, $selection_query['parameters'], 0, 1));
  140.       // Update the status field to sign as executed that link
  141.       // (The following two lines could be move to the end of the function i think without problems)
  142.       db_query("UPDATE {crawler_links} SET status = status + 1 WHERE status = %d AND crawler_id = %d", $status, $crawler_id);
  143.       $status++;
  144.       // Create a new object and parse the page
  145.       $obj = new DrupalSecurityScannerClass();
  146.       // Set the cookie
  147.       $session_cookie = variable_get('security_scanner_cookie','');
  148.       $obj->curl_options = array(
  149.         CURLOPT_COOKIE => $session_cookie,
  150.         CURLOPT_USERAGENT => 'security_scanner',
  151.       );
  152.       $obj->drupalGet($selected_results['path']);
  153.       $obj->parse();
  154.       $function_callback($obj, $selected_results);
  155.       $obj->curlClose();
  156.     }
  157.  }
  158.  
  159.   /**
  160.    *  Crawler: page processing function
  161.    */    
  162.   function security_scanner_page_processing($obj, $selected_results) {
  163.     global $base_url;
  164.     $links = $obj->elements->xpath('//a');
  165.     foreach($links as $link) {
  166.       $url_to_save = (string)$link->attributes()->href;
  167.       $absolute = getAbsoluteUrl($url_to_save);
  168.       // Get the page but check:
  169.       // a - if it's logout link, that makes me lose the cookie.
  170.       // b - if it's security scanner, skip
  171.       // c - if it's xss_injector, skip. That will launch the crawler
  172.       // d - if it's cron.php, that will make a loop
  173.       $parsed_url = parse_url($absolute);
  174.       if (($parsed_url['query'] != 'q=logout') && ($parsed_url['query'] != 'q=admin/settings/security_scanner') && ($parsed_url['query'] != 'q=admin/settings/xss_injector') && ($parsed_url['file'] != 'cron.php')) {  
  175.               if (substr($absolute, 0, strlen($base_url)) == $base_url) {
  176.                 // Here we use IGNORE to insert only one time a link into the table. ("path" is a unique index)
  177.           db_query("INSERT IGNORE INTO {crawler_links} (id, path, crawler_id, status) VALUES ('','%s','','')", $absolute);
  178.         }
  179.       }
  180.     }
  181.     // Get the forms inside the page
  182.     $inputs = $obj->elements->xpath("//input[@name='form_id']");
  183.     foreach($inputs as $input) {
  184.       $form_id = (string)$input->attributes()->id;
  185.       // Debug line! HAS TO BE REMOVED
  186.       echo $form_id."Form inserted! <br />";
  187.       // Here we use again IGNORE to insert only one time a form_id into the table. ("form_id" is the primary key)
  188.       db_query("INSERT IGNORE INTO {crawler_forms} VALUES ('%s','%d')", $form_id, $selected_results['id']);
  189.     }
  190.   }
  191.  
  192.   /**
  193.    *  Implementation of the crawler page.
  194.    */    
  195.   function security_scanner_cron() {
  196.     //  Check if this is the mode that we set from settings page.
  197.     if (variable_get('security_scanner_settings','') == '0') {
  198.       //  Check if the auth session cookie value is already into the db, otherwise call
  199.       //  the function that retrieve this (enable multithreading)
  200.       if (variable_get('security_scanner_cookie','') == '') {
  201.         drupal_security_scanner_get_auth_cookie();
  202.       }
  203.       // Preparing the query that has to be passed to the crawler_framework
  204.       $selection_query = array(
  205.         'fields' => array('l.id'),
  206.       );
  207.       echo "I'm crawling...<br />";
  208.       crawler_framework($selection_query, 0, 'security_scanner_page_processing');
  209.     }
  210.   }
  211.  
  212.   /**
  213.    *  Get the cookie of the admin and insert the first link into the table crawler_links.
  214.    *  There is an issue, I have to start the crawler from uid different than 1.    
  215.    */
  216.    function drupal_security_scanner_get_auth_cookie() {
  217.     $initial_path = user_pass_reset_url(user_load(1));
  218.     // Add sleep to go round a bug inside a drupal core function. Remove it when it's changed into core.
  219.     sleep(1);
  220.     //  Create a new object, set cURL options to call the function drupal_security_scanner_curl_headers that
  221.     //  saves into the variable table the admin cookie. Then set the cookie.
  222.     $obj = new DrupalSecurityScannerClass();
  223.     $obj->curl_options = array(
  224.       CURLOPT_HEADERFUNCTION => 'drupal_security_scanner_curl_headers',
  225.       CURLOPT_FOLLOWLOCATION => 0,
  226.     );
  227.     // Get the page with password reset and push submit button
  228.     $obj->drupalGet($initial_path);
  229.     $obj->drupalPost($initial_path,'',TRUE);
  230.     //  Add the first url into the crawler_links table.
  231.     db_query("INSERT INTO {crawler_links} (id, path, crawler_id, status) VALUES ('','%s','','')", url('admin', array('absolute' => TRUE)));
  232.     return true;
  233.   }
  234.  
  235.   /**
  236.    *  This function will extract headers and return the lenght.
  237.    */  
  238.  function drupal_security_scanner_curl_headers($ch = NULL, $header = NULL) {
  239.     static $headers = array();
  240.     if (!isset($ch)) {
  241.       return $headers;
  242.     }
  243.     if(!strncmp($header, "Set-Cookie:", 11)) {
  244.       //  get the cookie
  245.       $cookiestr = trim(substr($header, 11, -1));
  246.       $cookie = explode(';', $cookiestr);
  247.       variable_set('security_scanner_cookie', $cookie[0]);
  248.     }
  249.     return strlen($header);  
  250.   }
  251.  
  252.   /**
  253.    *  Implementation of hook _perm()
  254.    */  
  255.   function security_scanner_perm() {
  256.     return array('access scanner');
  257.   }
  258.  
  259.   /**
  260.    *  Implementation of hook _help()
  261.    */
  262.    function security_scanner_help($path, $arg) {
  263.      switch ($path) {
  264.        case 'security_scanner':
  265.        // Here is some help text for a custom page.
  266.          return t('This sentence contains all the letters in the English alphabet.');
  267.      }
  268.    }
  269. ?>