diff --git a/core/control/Director.php b/core/control/Director.php index 47b6cf66b..677730a4e 100755 --- a/core/control/Director.php +++ b/core/control/Director.php @@ -605,19 +605,55 @@ class Director { } /** - * Force the site to run on SSL. To use, call from _config.php. + * Force the site to run on SSL. * - * For example: + * To use, call from _config.php. For example: * * if(Director::isLive()) Director::forceSSL(); * + * + * If you don't want your entire site to be on SSL, you can pass an array of PCRE regular expression + * patterns for matching relative URLs. For example: + * + * if(Director::isLive()) Director::forceSSL(array('/^admin/', '/^Security/.*')); + * + * + * Note that the session data will be lost when moving from HTTP to HTTPS. + * It is your responsibility to ensure that this won't cause usability problems. + * + * CAUTION: This does not respect the site environment mode. You should check this + * as per the above examples using Director::isLive() or Director::isTest() for example. + * + * @return boolean|string String of URL when unit tests running, boolean FALSE if patterns don't match request URI */ - static function forceSSL() { - if((Director::protocol() != "https://") && !Director::isDev() && !Director::is_cli()) { + static function forceSSL($patterns = null) { + $matched = false; + + if($patterns) { + // protect portions of the site based on the pattern + $relativeURL = self::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI'])); + foreach($patterns as $pattern) { + if(preg_match($pattern, $relativeURL)) { + $matched = true; + break; + } + } + } else { + // protect the entire site + $matched = true; + } + + if($matched && !isset($_SERVER['HTTPS'])) { $destURL = str_replace('http:', 'https:', Director::absoluteURL($_SERVER['REQUEST_URI'])); - header("Location: $destURL", true, 301); - die("

Your browser is not accepting header redirects

Please click here"); + header("Location: $destURL"); + if(SapphireTest::is_running_test()) { + return $destURL; + } else { + die("

Your browser is not accepting header redirects

Please click here"); + } + } else { + return false; } } diff --git a/tests/control/DirectorTest.php b/tests/control/DirectorTest.php index c643dc048..420c8eba6 100644 --- a/tests/control/DirectorTest.php +++ b/tests/control/DirectorTest.php @@ -5,7 +5,7 @@ * * @todo test Director::alternateBaseFolder() */ -class DirectorTest extends SapphireTest { +class DirectorTest extends FunctionalTest { function setUp() { parent::setUp(); @@ -189,7 +189,53 @@ class DirectorTest extends SapphireTest { ) ); } - + + function testForceSSLProtectsEntireSite() { + $originalURI = $_SERVER['REQUEST_URI']; + $_SERVER['REQUEST_URI'] = Director::baseURL() . 'admin'; + $output = Director::forceSSL(); + $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); + + $_SERVER['REQUEST_URI'] = $originalURI; + $_SERVER['REQUEST_URI'] = Director::baseURL() . 'some-url'; + $output = Director::forceSSL(); + $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); + + $_SERVER['REQUEST_URI'] = $originalURI; + } + + function testForceSSLOnTopLevelPagePattern() { + $originalURI = $_SERVER['REQUEST_URI']; + $_SERVER['REQUEST_URI'] = Director::baseURL() . 'admin'; + $output = Director::forceSSL(array('/^admin/')); + $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); + + $_SERVER['REQUEST_URI'] = $originalURI; + } + + function testForceSSLOnSubPagesPattern() { + $originalURI = $_SERVER['REQUEST_URI']; + $_SERVER['REQUEST_URI'] = Director::baseURL() . 'Security/login'; + $output = Director::forceSSL(array('/^Security/')); + $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); + + $_SERVER['REQUEST_URI'] = $originalURI; + } + + function testForceSSLWithPatternDoesNotMatchOtherPages() { + $originalURI = $_SERVER['REQUEST_URI']; + $_SERVER['REQUEST_URI'] = Director::baseURL() . 'normal-page'; + $output = Director::forceSSL(array('/^admin/')); + $this->assertFalse($output); + + $_SERVER['REQUEST_URI'] = $originalURI; + $_SERVER['REQUEST_URI'] = Director::baseURL() . 'just-another-page/sub-url'; + $output = Director::forceSSL(array('/^admin/', '/^Security/')); + $this->assertFalse($output); + + $_SERVER['REQUEST_URI'] = $originalURI; + } + } class DirectorTestRequest_Controller extends Controller implements TestOnly {