CRM-16832. Do not redirect offsite when fed invalid keys.
authorChris Burgess <chris@giantrobot.co.nz>
Mon, 13 Jul 2015 00:45:51 +0000 (12:45 +1200)
committereileenmcnaugton <eileen@fuzion.co.nz>
Thu, 6 Aug 2015 01:15:45 +0000 (13:15 +1200)
URLs to test:

* http://civicrm.dev/civicrm/contribute/transact?qfKey=xxx&entryURL=http://evil.example.com/ should go to CIVICRM_UF_BASEURL
* http://civicrm.dev/civicrm/contribute/transact?qfKey=xxx&entryURL=/civicrm/contribute/transact%3Fid%3D1 should go to /civicrm/contribute/transact?id=1
* http://civicrm.dev/civicrm/contribute/transact?qfKey=xxx&entryURL=http://civicrm.dev/civicrm/contribute/transact%3Fid%3D1 should go to /civicrm/contribute/transact?id=1

CRM/Core/Controller.php
tests/phpunit/WebTest/Utils/RedirectTest.php [new file with mode: 0644]

index 7744c60d1f933974d8d2635cef034423126b8618..b07b398af84b50b57fa354bcdb1477b92defa924 100644 (file)
@@ -858,18 +858,25 @@ class CRM_Core_Controller extends HTML_QuickForm_Controller {
   }
 
   /**
-   * Instead of outputting a fatal error message, we'll just redirect to the entryURL if present
+   * Instead of outputting a fatal error message, we'll just redirect
+   * to the entryURL if present
    *
    * @return void
    */
   public function invalidKeyRedirect() {
-    if ($this->_entryURL) {
-      CRM_Core_Session::setStatus(ts('Your browser session has expired and we are unable to complete your form submission. We have returned you to the initial step so you can complete and resubmit the form. If you experience continued difficulties, please contact us for assistance.'));
-      return CRM_Utils_System::redirect($this->_entryURL);
-    }
-    else {
-      self::invalidKeyCommon();
+    if ($this->_entryURL && $url_parts = parse_url($this->_entryURL)) {
+      // CRM-16832: Ensure local redirects only.
+      if (!empty($url_parts['path'])) {
+        // Prepend a slash, but don't duplicate it.
+        $redirect_url = '/' . ltrim($url_parts['path'], '/');
+        if (!empty($url_parts['query'])) {
+          $redirect_url .= '?' . $url_parts['query'];
+        }
+        CRM_Core_Session::setStatus(ts('Your browser session has expired and we are unable to complete your form submission. We have returned you to the initial step so you can complete and resubmit the form. If you experience continued difficulties, please contact us for assistance.'));
+        return CRM_Utils_System::redirect($redirect_url);
+      }
     }
+    self::invalidKeyCommon();
   }
 
 }
diff --git a/tests/phpunit/WebTest/Utils/RedirectTest.php b/tests/phpunit/WebTest/Utils/RedirectTest.php
new file mode 100644 (file)
index 0000000..39d3829
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.6                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License along with this program; if not, contact CiviCRM LLC       |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+require_once 'CiviTest/CiviUnitTestCase.php';
+require_once 'CiviTest/CiviSeleniumSettings.php';
+
+/**
+ *  Include configuration
+ */
+define('CIVICRM_SETTINGS_PATH', __DIR__ . '/civicrm.settings.dist.php');
+define('CIVICRM_SETTINGS_LOCAL_PATH', __DIR__ . '/civicrm.settings.local.php');
+define('CIVICRM_WEBTEST', 1);
+
+if (file_exists(CIVICRM_SETTINGS_LOCAL_PATH)) {
+  require_once CIVICRM_SETTINGS_LOCAL_PATH;
+}
+require_once CIVICRM_SETTINGS_PATH;
+
+
+/**
+ * Check that we handle redirects appropriately.
+ */
+class WebTest_Utils_RedirectTest extends CiviUnitTestCase {
+  protected $url;
+  protected $ch;
+
+  /**
+   * @param string|null $name
+   */
+  public function __construct($name = NULL) {
+    parent::__construct($name);
+
+    $this->settings = new CiviSeleniumSettings();
+    if (property_exists($this->settings, 'serverStartupTimeOut') && $this->settings->serverStartupTimeOut) {
+      global $CiviSeleniumTestCase_polled;
+      if (!$CiviSeleniumTestCase_polled) {
+        $CiviSeleniumTestCase_polled = TRUE;
+        CRM_Utils_Network::waitForServiceStartup(
+          $this->drivers[0]->getHost(),
+          $this->drivers[0]->getPort(),
+          $this->settings->serverStartupTimeOut
+        );
+      }
+    }
+  }
+
+  protected function setUp() {
+    parent::setUp();
+    //URL should eventually be adapted for multisite
+    $this->url = $this->settings->sandboxURL;
+
+    $this->ch = curl_init();
+    curl_setopt($this->ch, CURLOPT_HEADER, FALSE);
+    curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, FALSE);
+    // curl_setopt($this->ch, CURLOPT_ENCODING, 'gzip');
+    // curl_setopt($this->ch, CURLOPT_VERBOSE, 0);
+  }
+
+  /**
+   *
+   */
+  private function tryRedirect($input_url, $expected_url) {
+    // file_put_contents('php://stderr', $input_url . "\n", FILE_APPEND);
+    $url = $this->url . '/' . $input_url;
+    $expected_url = $this->url . '/' . $expected_url;
+    curl_setopt($this->ch, CURLOPT_URL, $url);
+    $req = curl_exec($this->ch);
+    $this->assertEquals(0, curl_errno($this->ch), 'cURL error: ' . curl_error($this->ch));
+    if (!curl_errno($this->ch)) {
+      $info = curl_getinfo($this->ch);
+      // file_put_contents('php://stderr', print_r($info,1), FILE_APPEND);
+      $this->assertEquals($expected_url, $info['redirect_url']);
+      $this->assertEquals('302', $info['http_code']);
+    }
+  }
+
+  /**
+   * Handle onsite redirects with absolute URL.
+   */
+  public function testAbsoluteOnsiteRedirect() {
+    $this->tryRedirect("civicrm/contribute/transact?qfKey=xxx&entryURL={$this->url}/civicrm/contribute/transact%3Fid%3D1", 'civicrm/contribute/transact?id=1');
+  }
+
+  /**
+   * Handle onsite redirects with slash prefix and query params.
+   */
+  public function testOnsiteRedirectWithSlashPrefixAndQueryParams() {
+    $this->tryRedirect('civicrm/contribute/transact?qfKey=xxx&entryURL=/civicrm/contribute/transact%3Fid%3D1', 'civicrm/contribute/transact?id=1');
+  }
+
+  /**
+   * Handle onsite redirects with non-CiviCRM paths.
+   */
+  public function testOtherpathRedirect() {
+    $this->tryRedirect('civicrm/contribute/transact?qfKey=xxx&entryURL=asdf', 'asdf');
+  }
+
+  /**
+   * Handle offsite redirects without path as onsite redirects.
+   */
+  public function testOffsiteRedirectNoPath() {
+    $this->tryRedirect('civicrm/contribute/transact?qfKey=xxx&entryURL=http://evil.example.com/', '');
+  }
+
+  /**
+   * Handle offsite redirects with paths as onsite redirects.
+   */
+  public function testOffsiteRedirectWithPath() {
+    $this->tryRedirect('civicrm/contribute/transact?qfKey=xxx&entryURL=http://evil.example.com/civicrm', 'civicrm');
+  }
+
+}