From 59680b52e2f06b9d7968c1ce143253f2683c36a1 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 1 Nov 2012 15:57:00 +0100 Subject: [PATCH 01/30] Added composer.json --- composer.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..1aa80df55 --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "silverstripe/framework", + "description": "The SilverStripe Framework", + "type": "silverstripe-module", + "keywords": ["silverstripe", "framework"], + "homepage": "http://silverstripe.org", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "SilverStripe", + "homepage": "http://silverstripe.com" + }, + { + "name": "The SilverStripe Community", + "homepage": "http://silverstripe.org" + } + ], + "require": { + "php": ">=5.2.4", + "composer/installers": "*" + } +} \ No newline at end of file From 22095dae6c7fee69928402bd8d0369c4af0c033b Mon Sep 17 00:00:00 2001 From: Mateusz Uzdowski Date: Fri, 9 Nov 2012 13:37:35 +1300 Subject: [PATCH 02/30] API Hash autologin tokens before storing in the database. Backported from 3.0, cc423c38fbc6755f4e29024590c1b42092b3a621. --- security/Member.php | 81 +++++++++++++++++++++----- security/MemberLoginForm.php | 6 +- security/PasswordEncryptor.php | 4 +- security/RandomGenerator.php | 22 +++++-- security/Security.php | 40 ++++++++----- security/SecurityToken.php | 4 +- tests/security/MemberTest.php | 31 +++++++++- tests/security/RandomGeneratorTest.php | 10 ++-- tests/security/SecurityTest.php | 9 ++- 9 files changed, 158 insertions(+), 49 deletions(-) diff --git a/security/Member.php b/security/Member.php index a26a75d93..5e6a8d836 100755 --- a/security/Member.php +++ b/security/Member.php @@ -11,11 +11,11 @@ class Member extends DataObject { 'Surname' => 'Varchar', 'Email' => 'Varchar', 'Password' => 'Varchar(160)', - 'RememberLoginToken' => 'Varchar(50)', + 'RememberLoginToken' => 'Varchar(160)', // Note: this currently holds a hash, not a token. 'NumVisit' => 'Int', 'LastVisited' => 'SS_Datetime', 'Bounced' => 'Boolean', // Note: This does not seem to be used anywhere. - 'AutoLoginHash' => 'Varchar(50)', + 'AutoLoginHash' => 'Varchar(160)', 'AutoLoginExpired' => 'SS_Datetime', // This is an arbitrary code pointing to a PasswordEncryptor instance, // not an actual encryption algorithm. @@ -327,9 +327,11 @@ class Member extends DataObject { $this->NumVisit++; if($remember) { + // Store the hash and give the client the cookie with the token. $generator = new RandomGenerator(); - $token = $generator->generateHash('sha1'); - $this->RememberLoginToken = $token; + $token = $generator->randomToken('sha1'); + $hash = $this->encryptWithUserSettings($token); + $this->RememberLoginToken = $hash; Cookie::set('alc_enc', $this->ID . ':' . $token, 90, null, null, null, true); } else { $this->RememberLoginToken = null; @@ -387,7 +389,8 @@ class Member extends DataObject { $member = DataObject::get_one("Member", "\"Member\".\"ID\" = '$SQL_uid'"); // check if autologin token matches - if($member && (!$member->RememberLoginToken || $member->RememberLoginToken != $token)) { + $hash = $member->encryptWithUserSettings($token); + if($member && (!$member->RememberLoginToken || $member->RememberLoginToken != $hash)) { $member = null; } @@ -398,7 +401,9 @@ class Member extends DataObject { if(self::$login_marker_cookie) Cookie::set(self::$login_marker_cookie, 1, 0, null, null, false, true); $generator = new RandomGenerator(); - $member->RememberLoginToken = $generator->generateHash('sha1'); + $token = $generator->randomToken('sha1'); + $hash = $member->encryptWithUserSettings($token); + $member->RememberLoginToken = $hash; Cookie::set('alc_enc', $member->ID . ':' . $token, 90, null, null, false, true); $member->NumVisit++; @@ -430,27 +435,76 @@ class Member extends DataObject { $this->extend('memberLoggedOut'); } + /** + * Utility for generating secure password hashes for this member. + */ + public function encryptWithUserSettings($string) { + if (!$string) return null; + + // If the algorithm or salt is not available, it means we are operating + // on legacy account with unhashed password. Do not hash the string. + if (!$this->PasswordEncryption) { + return $string; + } + + // We assume we have PasswordEncryption and Salt available here. + $e = PasswordEncryptor::create_for_algorithm($this->PasswordEncryption); + return $e->encrypt($string, $this->Salt); + + } /** - * Generate an auto login hash - * - * This creates an auto login hash that can be used to reset the password. + * Generate an auto login token which can be used to reset the password, + * at the same time hashing it and storing in the database. * * @param int $lifetime The lifetime of the auto login hash in days (by default 2 days) * + * @returns string Token that should be passed to the client (but NOT persisted). + * * @todo Make it possible to handle database errors such as a "duplicate key" error */ - function generateAutologinHash($lifetime = 2) { - + public function generateAutologinTokenAndStoreHash($lifetime = 2) { do { $generator = new RandomGenerator(); - $hash = $generator->generateHash('sha1'); + $token = $generator->randomToken(); + $hash = $this->encryptWithUserSettings($token); } while(DataObject::get_one('Member', "\"AutoLoginHash\" = '$hash'")); $this->AutoLoginHash = $hash; $this->AutoLoginExpired = date('Y-m-d', time() + (86400 * $lifetime)); $this->write(); + + return $token; + } + + /** + * @deprecated 2.4 + */ + public function generateAutologinHash($lifetime = 2) { + user_error( + 'Member::generateAutologinHash is deprecated - tokens are no longer saved directly into the database '. + 'in plaintext. Use the return value of the Member::generateAutologinTokenAndHash to get the token '. + 'instead.', + E_USER_ERROR); + } + + /** + * Check the token against the member. + * + * @param string $autologinToken + * + * @returns bool Is token valid? + */ + public function validateAutoLoginToken($autologinToken) { + $hash = $this->encryptWithUserSettings($autologinToken); + + $member = DataObject::get_one( + 'Member', + "\"AutoLoginHash\"='" . $hash . "' AND \"AutoLoginExpired\" > " . DB::getConn()->now() + ); + + return (bool)$member; } /** @@ -469,7 +523,6 @@ class Member extends DataObject { return $member; } - /** * Send signup, change password or forgot password informations to an user * @@ -1980,4 +2033,4 @@ class Member_DatetimeOptionsetField extends OptionsetField { } } -} \ No newline at end of file +} diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index d46a10dea..6e67e1dd4 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -229,12 +229,12 @@ JS $member = DataObject::get_one('Member', "\"Email\" = '{$SQL_email}'"); if($member) { - $member->generateAutologinHash(); + $token = $member->generateAutologinTokenAndStoreHash(); $member->sendInfo( 'forgotPassword', array( - 'PasswordResetLink' => Security::getPasswordResetLink($member->AutoLoginHash) + 'PasswordResetLink' => Security::getPasswordResetLink($member, $token) ) ); @@ -254,4 +254,4 @@ JS } } -?> \ No newline at end of file +?> diff --git a/security/PasswordEncryptor.php b/security/PasswordEncryptor.php index 157889017..3ef42e92a 100644 --- a/security/PasswordEncryptor.php +++ b/security/PasswordEncryptor.php @@ -91,7 +91,7 @@ abstract class PasswordEncryptor { */ function salt($password, $member = null) { $generator = new RandomGenerator(); - return substr($generator->generateHash('sha1'), 0, 50); + return substr($generator->randomToken('sha1'), 0, 50); } /** @@ -237,4 +237,4 @@ class PasswordEncryptor_None extends PasswordEncryptor { * @package sapphire * @subpackage security */ -class PasswordEncryptor_NotFoundException extends Exception {} \ No newline at end of file +class PasswordEncryptor_NotFoundException extends Exception {} diff --git a/security/RandomGenerator.php b/security/RandomGenerator.php index fe4ede073..a211ca411 100644 --- a/security/RandomGenerator.php +++ b/security/RandomGenerator.php @@ -57,16 +57,26 @@ class RandomGenerator { // Fallback to good old mt_rand() return uniqid(mt_rand(), true); } - + /** - * Generates a hash suitable for manual session identifiers, CSRF tokens, etc. + * Generates a random token that can be used for session IDs, CSRF tokens etc., based on + * hash algorithms. + * + * If you are using it as a password equivalent (e.g. autologin token) do NOT store it + * in the database as a plain text but encrypt it with Member::encryptWithUserSettings. * * @param String $algorithm Any identifier listed in hash_algos() (Default: whirlpool) - * If possible, choose a slow algorithm which complicates brute force attacks. + * * @return String Returned length will depend on the used $algorithm */ - function generateHash($algorithm = 'whirlpool') { + function randomToken($algorithm = 'whirlpool') { return hash($algorithm, $this->generateEntropy()); } - -} \ No newline at end of file + + /** + * @deprecated 3.1 + */ + public function generateHash($algorithm = 'whirlpool') { + return $this->randomToken($algorithm); + } +} diff --git a/security/Security.php b/security/Security.php index f1d659c25..01a1b2a16 100644 --- a/security/Security.php +++ b/security/Security.php @@ -502,13 +502,18 @@ class Security extends Controller { /** - * Create a link to the password reset form + * Create a link to the password reset form. * - * @param string $autoLoginHash The auto login hash + * GET parameters used: + * - m: member ID + * - t: plaintext token + * + * @param Member $member Member object associated with this link. + * @param string $autoLoginHash The auto login token. */ - public static function getPasswordResetLink($autoLoginHash) { - $autoLoginHash = urldecode($autoLoginHash); - return self::Link('changepassword') . "?h=$autoLoginHash"; + public static function getPasswordResetLink($member, $autologinToken) { + $autologinToken = urldecode($autologinToken); + return self::Link('changepassword') . "?m={$member->ID}&t=$autologinToken"; } /** @@ -531,15 +536,22 @@ class Security extends Controller { $controller = new Page_Controller($tmpPage); $controller->init(); - // First load with hash: Redirect to same URL without hash to avoid referer leakage - if(isset($_REQUEST['h']) && Member::member_from_autologinhash($_REQUEST['h'])) { - // The auto login hash is valid, store it for the change password form. - // Temporary value, unset in ChangePasswordForm - Session::set('AutoLoginHash', $_REQUEST['h']); + // Extract the member from the URL. + $member = null; + if (isset($_REQUEST['m'])) { + $member = DataObject::get_by_id('Member', (int)$_REQUEST['m']); + } + + // Check whether we are merely changin password, or resetting. + if(isset($_REQUEST['t']) && $member && $member->validateAutoLoginToken($_REQUEST['t'])) { + // On first valid password reset request redirect to the same URL without hash to avoid referrer leakage. + + // Store the hash for the change password form. Will be unset after reload within the ChangePasswordForm. + Session::set('AutoLoginHash', $member->encryptWithUserSettings($_REQUEST['t'])); return $this->redirect($this->Link('changepassword')); - // Redirection target after "First load with hash" } elseif(Session::get('AutoLoginHash')) { + // Subsequent request after the "first load with hash" (see previous if clause). $customisedController = $controller->customise(array( 'Content' => '

' . @@ -548,15 +560,15 @@ class Security extends Controller { 'Form' => $this->ChangePasswordForm(), )); } elseif(Member::currentUser()) { - // let a logged in user change his password + // Logged in user requested a password change form. $customisedController = $controller->customise(array( 'Content' => '

' . _t('Security.CHANGEPASSWORDBELOW', 'You can change your password below.') . '

', 'Form' => $this->ChangePasswordForm())); } else { - // show an error message if the auto login hash is invalid and the + // show an error message if the auto login token is invalid and the // user is not logged in - if(isset($_REQUEST['h'])) { + if(!isset($_REQUEST['t']) || !$member) { $customisedController = $controller->customise( array('Content' => sprintf( diff --git a/security/SecurityToken.php b/security/SecurityToken.php index 964c47de4..a3cba5ca8 100644 --- a/security/SecurityToken.php +++ b/security/SecurityToken.php @@ -206,7 +206,7 @@ class SecurityToken extends Object { */ protected function generate() { $generator = new RandomGenerator(); - return $generator->generateHash('sha1'); + return $generator->randomToken('sha1'); } } @@ -273,4 +273,4 @@ class NullSecurityToken extends SecurityToken { return null; } -} \ No newline at end of file +} diff --git a/tests/security/MemberTest.php b/tests/security/MemberTest.php index e7d5a8b9a..fc2a7f3c0 100644 --- a/tests/security/MemberTest.php +++ b/tests/security/MemberTest.php @@ -557,6 +557,35 @@ class MemberTest extends FunctionalTest { ); } + public function testGenerateAutologinTokenAndStoreHash() { + $enc = new PasswordEncryptor_PHPHash('sha1'); + + $m = new Member(); + $m->PasswordEncryption = 'sha1'; + $m->Salt = $enc->salt('123'); + + $token = $m->generateAutologinTokenAndStoreHash(); + + $this->assertEquals($m->encryptWithUserSettings($token), $m->AutoLoginHash, 'Stores the token as ahash.'); + } + + public function testValidateAutoLoginToken() { + $enc = new PasswordEncryptor_PHPHash('sha1'); + + $m1 = new Member(); + $m1->PasswordEncryption = 'sha1'; + $m1->Salt = $enc->salt('123'); + $m1Token = $m1->generateAutologinTokenAndStoreHash(); + + $m2 = new Member(); + $m2->PasswordEncryption = 'sha1'; + $m2->Salt = $enc->salt('456'); + $m2Token = $m2->generateAutologinTokenAndStoreHash(); + + $this->assertTrue($m1->validateAutoLoginToken($m1Token), 'Passes token validity test against matching member.'); + $this->assertFalse($m2->validateAutoLoginToken($m1Token), 'Fails token validity test against other member.'); + } + /** * Add the given array of member extensions as class names. * This is useful for re-adding extensions after being removed @@ -617,4 +646,4 @@ class MemberTest_EditingAllowedDeletingDeniedExtension extends DataObjectDecorat return false; } -} \ No newline at end of file +} diff --git a/tests/security/RandomGeneratorTest.php b/tests/security/RandomGeneratorTest.php index 2a41ecdfa..f1fb83dd6 100644 --- a/tests/security/RandomGeneratorTest.php +++ b/tests/security/RandomGeneratorTest.php @@ -14,14 +14,14 @@ class RandomGeneratorTest extends SapphireTest { function testGenerateHash() { $r = new RandomGenerator(); - $this->assertNotNull($r->generateHash()); - $this->assertNotEquals($r->generateHash(), $r->generateHash()); + $this->assertNotNull($r->randomToken()); + $this->assertNotEquals($r->randomToken(), $r->randomToken()); } function testGenerateHashWithAlgorithm() { $r = new RandomGenerator(); - $this->assertNotNull($r->generateHash('md5')); - $this->assertNotEquals($r->generateHash(), $r->generateHash('md5')); + $this->assertNotNull($r->randomToken('md5')); + $this->assertNotEquals($r->randomToken(), $r->randomToken('md5')); } -} \ No newline at end of file +} diff --git a/tests/security/SecurityTest.php b/tests/security/SecurityTest.php index 1971fa064..c8bdbe221 100644 --- a/tests/security/SecurityTest.php +++ b/tests/security/SecurityTest.php @@ -196,7 +196,12 @@ class SecurityTest extends FunctionalTest { // Load password link from email $admin = DataObject::get_by_id('Member', $admin->ID); $this->assertNotNull($admin->AutoLoginHash, 'Hash has been written after lost password'); - $response = $this->get('Security/changepassword/?h=' . $admin->AutoLoginHash); + + // We don't have access to the token - generate a new token and hash pair. + $token = $admin->generateAutologinTokenAndStoreHash(); + + // Check. + $response = $this->get('Security/changepassword/?m='.$admin->ID.'&t=' . $token); $this->assertEquals(302, $response->getStatusCode()); $this->assertEquals(Director::baseUrl() . 'Security/changepassword', $response->getHeader('Location')); @@ -397,4 +402,4 @@ class SecurityTest extends FunctionalTest { } } -?> \ No newline at end of file +?> From 21791e4114d1ab7af403918703cadd60a6b26431 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 9 Nov 2012 12:58:56 +0100 Subject: [PATCH 03/30] Added travis support --- .travis.yml | 28 ++++++++++++++++++++++++ tests/travis/_config.php | 22 +++++++++++++++++++ tests/travis/_manifest_exclude | 0 tests/travis/_ss_environment.php | 37 ++++++++++++++++++++++++++++++++ tests/travis/before_script | 9 ++++++++ 5 files changed, 96 insertions(+) create mode 100644 .travis.yml create mode 100644 tests/travis/_config.php create mode 100644 tests/travis/_manifest_exclude create mode 100644 tests/travis/_ss_environment.php create mode 100755 tests/travis/before_script diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..c4811ab2f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +language: php +php: + - 5.3 + +env: + - TESTDB=MYSQL + - TESTDB=PGSQL + +before_script: + - phpenv rehash + - ./tests/travis/before_script ~/builds/ss + - cd ~/builds/ss + +script: + - phpunit -c phpunit.xml.dist + +branches: + except: + - 2.1 + - 2.2 + - 2.3 + - post-2.4 + - translation-staging + +notifications: + irc: + channels: + - "irc.freenode.org#silverstripe" \ No newline at end of file diff --git a/tests/travis/_config.php b/tests/travis/_config.php new file mode 100644 index 000000000..7b5da0dc3 --- /dev/null +++ b/tests/travis/_config.php @@ -0,0 +1,22 @@ + Date: Fri, 9 Nov 2012 13:14:17 +0100 Subject: [PATCH 04/30] Added README with build status --- README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..44920ab33 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# SilverStripe Framework (a.k.a "sapphire") + +[![Build Status](https://secure.travis-ci.org/silverstripe/sapphire.png?branch=2.4)](https://travis-ci.org/silverstripe/sapphire) \ No newline at end of file From 7db928ba17f9de54e49af7e9a70e826f4a36a7a7 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 9 Nov 2012 13:31:22 +0100 Subject: [PATCH 05/30] Added cms to travis requirements --- tests/travis/before_script | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/travis/before_script b/tests/travis/before_script index 53e1ba4d9..294568525 100755 --- a/tests/travis/before_script +++ b/tests/travis/before_script @@ -1,5 +1,6 @@ BUILD_DIR=$1 git clone --depth=100 --branch 2.4 --quiet git://github.com/silverstripe/silverstripe-installer.git $BUILD_DIR +git clone --depth=100 --branch 2.4 --quiet git://github.com/silverstripe/silverstripe-cms.git $BUILD_DIR/cms git clone --depth=100 --branch 1.2 --quiet git://github.com/silverstripe-labs/silverstripe-sqlite3.git $BUILD_DIR/sqlite3 git clone --depth=100 --branch 1.0 --quiet git://github.com/silverstripe/silverstripe-postgresql.git $BUILD_DIR/postgresql cp ./tests/travis/_ss_environment.php $BUILD_DIR From 326036a501795048cff49f2687c2f0da1402f194 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 28 Nov 2012 15:35:09 +0100 Subject: [PATCH 06/30] Excluded or removed tests relying on actual webserver routing The "sanitychecks" group excludes through phpunit.xml.dist. Removed RestfulService->testHttpErrorWithoutCache() since its not sufficiently isolated in terms of testing. Has been refactored in 3.x, but too intrusive to backport. Changes mainly necessary to get Travis builds passing, since we don't want to start mucking around with dynamically generated file-to-url mappings just to get *unit* tests passing - as opposed to integration-testing the whole environment incl. webserver. --- tests/WebserverRoutingTest.php | 2 ++ tests/api/RestfulServiceTest.php | 38 +++++--------------------------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/tests/WebserverRoutingTest.php b/tests/WebserverRoutingTest.php index fc82efc37..641dbe499 100644 --- a/tests/WebserverRoutingTest.php +++ b/tests/WebserverRoutingTest.php @@ -8,6 +8,8 @@ * @todo Exclude this test from a standard test run - not all test environments * might have a webserver installed, or have it accessible for HTTP requests * from localhost. + * + * @group sanitychecks * * @package sapphire * @subpackage tests diff --git a/tests/api/RestfulServiceTest.php b/tests/api/RestfulServiceTest.php index 76f943a4a..b2f503cd3 100644 --- a/tests/api/RestfulServiceTest.php +++ b/tests/api/RestfulServiceTest.php @@ -93,14 +93,14 @@ class RestfulServiceTest extends SapphireTest { * @expectedException PHPUnit_Framework_Error */ function testIncorrectData() { - $connection = new RestfulService(Director::absoluteBaseURL(), 0); - $test1 = $connection->request('RestfulServiceTest_Controller/invalid?usetestmanifest=1&flush=1'); + $connection = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL(), 0); + $test1 = $connection->request('RestfulServiceTest_Controller/invalid'); $test1->xpath("\\fail"); } function testHttpErrorWithoutCache() { - $connection = new RestfulService(Director::absoluteBaseURL(), 0); - $response = $connection->request('RestfulServiceTest_Controller/httpErrorWithoutCache?usetestmanifest=1&flush=1'); + $connection = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL(), 0); + $response = $connection->request('RestfulServiceTest_Controller/httpErrorWithoutCache'); $this->assertEquals(400, $response->getStatusCode()); $this->assertFalse($response->getCachedBody()); @@ -108,30 +108,6 @@ class RestfulServiceTest extends SapphireTest { } - function testHttpErrorWithCache() { - $subUrl = 'RestfulServiceTest_Controller/httpErrorWithCache?usetestmanifest=1&flush=1'; - $connection = new RestfulService(Director::absoluteBaseURL(), 0); - $this->createFakeCachedResponse($connection, $subUrl); - $response = $connection->request($subUrl); - - $this->assertEquals(400, $response->getStatusCode()); - $this->assertEquals("Cache response body",$response->getCachedBody()); - $this->assertContains("HTTP Error", $response->getBody()); - - } - - /** - * Simulate cached response file for testing error requests that are supposed to have cache files - */ - private function createFakeCachedResponse($connection, $subUrl) { - $fullUrl = $connection->getAbsoluteRequestURL($subUrl); - $cachedir = TEMP_FOLDER; // Default silverstripe cache - $cache_file = md5($fullUrl); // Encoded name of cache file - $cache_path = $cachedir."/xmlresponse_$cache_file"; - $cacheResponse = new RestfulService_Response("Cache response body"); - $store = serialize($cacheResponse); - file_put_contents($cache_path, $store); - } } class RestfulServiceTest_Controller extends Controller { @@ -218,7 +194,7 @@ XML; class RestfulServiceTest_MockRestfulService extends RestfulService { public $session = null; - + public function request($subURL = '', $method = "GET", $data = null, $headers = null) { if(!$this->session) { @@ -263,7 +239,6 @@ class RestfulServiceTest_MockRestfulService extends RestfulService { else $postVars = $data; $responseFromDirector = Director::test($url, $postVars, $this->session, $method, $body, $headers); - $response = new RestfulService_Response( $responseFromDirector->getBody(), $responseFromDirector->getStatusCode() @@ -271,5 +246,4 @@ class RestfulServiceTest_MockRestfulService extends RestfulService { return $response; } -} -?> +} \ No newline at end of file From f49f1ff5df283b7324cbcd5840a8ea05ed727ce8 Mon Sep 17 00:00:00 2001 From: Simon Welsh Date: Thu, 29 Nov 2012 08:21:05 +1300 Subject: [PATCH 07/30] API Rename Transliterator to SS_Transliterator to remove conflict with Intl extension --- core/model/SiteTree.php | 2 +- core/model/Transliterator.php | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/model/SiteTree.php b/core/model/SiteTree.php index 69c198470..4df66e745 100755 --- a/core/model/SiteTree.php +++ b/core/model/SiteTree.php @@ -1517,7 +1517,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid */ function generateURLSegment($title){ $t = (function_exists('mb_strtolower')) ? mb_strtolower($title) : strtolower($title); - $t = Object::create('Transliterator')->toASCII($t); + $t = Object::create('SS_Transliterator')->toASCII($t); $t = str_replace('&','-and-',$t); $t = str_replace('&','-and-',$t); $t = ereg_replace('[^A-Za-z0-9]+','-',$t); diff --git a/core/model/Transliterator.php b/core/model/Transliterator.php index 6592761f0..62b0be90d 100644 --- a/core/model/Transliterator.php +++ b/core/model/Transliterator.php @@ -6,14 +6,14 @@ * Usage: * * - * $tr = new Transliterator(); + * $tr = new SS_Transliterator(); * $ascii = $tr->toASCII($unicode); * * * @package sapphire * @subpackage model */ -class Transliterator { +class SS_Transliterator { /** * Allow the use of iconv() to perform transliteration. Set to false to disable. * Even if this variable is true, iconv() won't be used if it's not installed. @@ -58,4 +58,8 @@ class Transliterator { protected function useIconv($source) { return iconv("utf-8", "us-ascii//IGNORE//TRANSLIT", $source); } -} \ No newline at end of file +} + +if(!class_exists('Transliterator')) { + class Transliterator extends SS_Transliterator {} +} From f86bd977a4e7ddebf9b3089d34679e1f2487bef2 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 4 Dec 2012 12:36:43 +0100 Subject: [PATCH 08/30] Fixed DateTest timezone settings Backport from 3.x, see d1a9e2b3 and 0aeda5c9 --- tests/fieldtypes/DateTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/fieldtypes/DateTest.php b/tests/fieldtypes/DateTest.php index 989eb623d..89b88be7d 100644 --- a/tests/fieldtypes/DateTest.php +++ b/tests/fieldtypes/DateTest.php @@ -10,14 +10,14 @@ class DateTest extends SapphireTest { function setUp() { // Set timezone to support timestamp->date conversion. // We can't use date_default_timezone_set() as its not supported prior to PHP 5.2 - $this->originalTZ = ini_get('date.timezone'); - ini_set('date.timezone','Pacific/Auckland'); + $this->originalTZ = date_default_timezone_get(); + date_default_timezone_set('Pacific/Auckland'); parent::setUp(); } function tearDown() { - ini_set('date.timezone',$this->originalTZ); + date_default_timezone_set($this->originalTZ); parent::tearDown(); } From d1e65b5657020f05803bee20ea40d9f76c64800a Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 4 Dec 2012 17:21:53 +0100 Subject: [PATCH 09/30] Support for composer-created themes dir structure Due to git limitations, we can't check out the blackcandy "parent" theme into themes/blackcandy/ directly, since that would require sharing paths with git repositories of other themes. --- dev/install/config-form.html | 8 ++++++++ dev/install/php5-required.html | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/dev/install/config-form.html b/dev/install/config-form.html index a0d8ade26..8a3501019 100644 --- a/dev/install/config-form.html +++ b/dev/install/config-form.html @@ -5,9 +5,17 @@ + + + + + + + + diff --git a/dev/install/php5-required.html b/dev/install/php5-required.html index fe072ffbc..693ff01ea 100644 --- a/dev/install/php5-required.html +++ b/dev/install/php5-required.html @@ -1,9 +1,17 @@ PHP 5 is required + + + + + + + + From 3be9499c3aaf51a906c44c29c2bf92a1ea28b328 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 4 Dec 2012 17:27:05 +0100 Subject: [PATCH 10/30] Fixed HTML syntax in config-form.html --- dev/install/config-form.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dev/install/config-form.html b/dev/install/config-form.html index 8a3501019..caf133643 100644 --- a/dev/install/config-form.html +++ b/dev/install/config-form.html @@ -7,17 +7,17 @@ - - - + + + - - - + + + - - + +
From 3fad49e2c00a1334553f0da2ac28ea97b224c2a7 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 4 Dec 2012 22:45:52 +0100 Subject: [PATCH 11/30] 2.4.9 changelog --- docs/en/changelogs/2.4.9.md | 46 +++++++++++++++++++++++++++++++++++++ docs/en/changelogs/index.md | 4 ++++ 2 files changed, 50 insertions(+) create mode 100644 docs/en/changelogs/2.4.9.md diff --git a/docs/en/changelogs/2.4.9.md b/docs/en/changelogs/2.4.9.md new file mode 100644 index 000000000..6b405ee37 --- /dev/null +++ b/docs/en/changelogs/2.4.9.md @@ -0,0 +1,46 @@ +# 2.4.9 (2012-12-04) # + +## Overview ## + +This release provides security fixes to make password hashes more secure, +and fixes a regression around duplicated CMS actions. + +## Upgrading + +Impact of the upgrade: + +* Reset password email links generated prior to 2.4.9 will cease to work. +* Users who use the "remember me" login feature will have to log in again. + +API changes related security patch 22095dae: + +* `Member::generateAutologinHash` is deprecated. You can no longer get the autologin token from `AutoLoginHash` field in `Member`. Instead use the return value of the `Member::generateAutologinTokenAndStoreHash` and do not persist it. +* `Security::getPasswordResetLink` now requires `Member` object as the first parameter. The password reset URL GET parameters have changed from only `h` (for hash) to `m` (for member ID) and `t` (for plaintext token). +* `RandomGenerator::generateHash` will be deprecated with 3.1. Rename the function call to `RandomGenerator::randomToken`. + +### Security: Hash autologin tokens before storing in the database. + +Severity: Moderate + +Autologin tokens (remember me and reset password) are stored in the database as a plain text. +If attacker obtained the database he would be able to gain access to accounts that have requested a password change, or have "remember me" enabled. + +## Details + +### Bugfixes + + * 2012-11-21 [6eb597a](https://github.com/silverstripe/silverstripe-cms/commit/6eb597a) ed travis.yml paths (Ingo Schommer) + * 2012-11-21 [41aec54](https://github.com/silverstripe/silverstripe-cms/commit/41aec54) Consistently use FormResponse in CMS JavaScript (fixes #8036) (Ingo Schommer) + * 2012-11-09 [a2501ad](https://github.com/silverstripe/silverstripe-installer/commit/a2501ad) ed bootstrap.php path in phpunit.xml.dist (Ingo Schommer) + +### Other + + * 2012-12-04 [2ea9f26](https://github.com/silverstripe/silverstripe-installer/commit/2ea9f26) Support for composer-created themes dir structure (Ingo Schommer) + * 2012-12-04 [75e58c9](https://github.com/silverstripe/silverstripe-cms/commit/75e58c9) More graceful handling of missing GET data in ModelAdmin (Ingo Schommer) + * 2012-12-04 [c802659](https://github.com/silverstripe/silverstripe-installer/commit/c802659) Relaxed composer version requirements so that stable releases can be created. (Sam Minnee) + * 2012-11-28 [9fa3c52](https://github.com/silverstripe/silverstripe-installer/commit/9fa3c52) Exclude vendor/ folder from default phpunit run (Ingo Schommer) + * 2012-11-09 [3f24b0f](https://github.com/silverstripe/silverstripe-cms/commit/3f24b0f) Added README with build status (Ingo Schommer) + * 2012-11-09 [65793e2](https://github.com/silverstripe/silverstripe-cms/commit/65793e2) Added travis support (Ingo Schommer) + * 2012-11-08 [f8e860f](https://github.com/silverstripe/silverstripe-installer/commit/f8e860f) Removed .mergesources.yml, not used since the dark SVN days (Ingo Schommer) + * 2012-11-01 [157a275](https://github.com/silverstripe/silverstripe-installer/commit/157a275) Removed custom repo sources from composer.json (Ingo Schommer) + * 2012-11-01 [7abb6ec](https://github.com/silverstripe/silverstripe-cms/commit/7abb6ec) Added composer.json (Ingo Schommer) \ No newline at end of file diff --git a/docs/en/changelogs/index.md b/docs/en/changelogs/index.md index 75b7346ee..d554cb064 100644 --- a/docs/en/changelogs/index.md +++ b/docs/en/changelogs/index.md @@ -9,6 +9,10 @@ For information on how to upgrade to newer versions consult the [upgrading](/ins ## Stable Releases + * [2.4.9](2.4.9) - 2012-12-04 + * [2.4.8](2.4.8) - 2012-10-30 + * [2.4.7](2.4.7) - 2012-02-01 + * [2.4.6](2.4.6) - 2011-10-17 * [2.4.5](2.4.5) - 2 February 2011 * [2.4.4](2.4.4) - 21 December 2010 * [2.4.3](2.4.3) - 11 November 2010 From 44c41085b97a8574af9458d2cffe88dc2d5a5060 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Sun, 6 Jan 2013 22:32:59 +0100 Subject: [PATCH 12/30] Copying request params before Core.php exec in PHPUnit bootstrap Same behaviour as with 3.0 bootstrap.php and the 2.4 cli-script.php (which it is based on). This allows to use GET switches which are evaluated in _config.php files, e.g. db= settings for running tests with various DBs without changing the underlying PHP config. --- tests/bootstrap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e4e631958..83f109ac8 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -19,8 +19,6 @@ function silverstripe_test_autoload($className) { } spl_autoload_register('silverstripe_test_autoload'); -require_once(getcwd()."/sapphire/core/Core.php"); - // Copied from cli-script.php, to enable same behaviour through phpunit runner. if(isset($_SERVER['argv'][2])) { $args = array_slice($_SERVER['argv'],2); @@ -37,6 +35,8 @@ if(isset($_SERVER['argv'][2])) { $_REQUEST = $_GET; } +require_once(getcwd()."/sapphire/core/Core.php"); + // Now set a fake REQUEST_URI $_SERVER['REQUEST_URI'] = BASE_URL . '/dev/tests/all'; From 78d21b511c89d03e56919daaa0740da36fa2bdda Mon Sep 17 00:00:00 2001 From: Fred Condo Date: Fri, 14 Dec 2012 14:26:13 -0800 Subject: [PATCH 13/30] Update documentation of nginx configuration - Avoid using "if" to check for file existence (use try_files instead) - Replicate the behavior of the .htaccess files - TODO: get static error pages to work --- docs/en/installation/nginx.md | 110 ++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 24 deletions(-) diff --git a/docs/en/installation/nginx.md b/docs/en/installation/nginx.md index 6dffc781e..c7ba04a50 100644 --- a/docs/en/installation/nginx.md +++ b/docs/en/installation/nginx.md @@ -1,35 +1,97 @@ # Nginx -These instructions are also covered on the [Nginx Wiki](http://wiki.nginx.org/SilverStripe) +These instructions are also covered in less detail on the +[Nginx Wiki](http://wiki.nginx.org/SilverStripe). -The prerequisite is that you have already installed Nginx and you are able to run PHP files via the FastCGI-wrapper from -Nginx. +The prerequisite is that you have already installed Nginx and you are +able to run PHP files via the FastCGI-wrapper from Nginx. -Now you need to setup a virtual host in Nginx with the following configuration settings: +Now you need to set up a virtual host in Nginx with the following +configuration settings: server { - listen 80; - server_name yoursite.com; - - root /home/yoursite.com/httpdocs; - index index.html index.php; - - if (!-f $request_filename) { - rewrite ^/(.*?)(\?|$)(.*)$ /sapphire/main.php?url=$1&$3 last; - } - - error_page 404 /sapphire/main.php; - - location ~ \.php$ { - fastcgi_pass 127.0.0.1:9000; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME /home/yoursite.com/httpdocs$fastcgi_script_name; - include fastcgi_params; - } + listen 80; + + # SSL configuration (optional, but recommended for security) + include ssl + + root /var/www/example.com; + index index.php index.html index.htm; + + server_name example.com; + + include silverstripe3; + include htaccess; + } + +Here is the include file `silverstripe3`: + + location / { + try_files $uri @silverstripe; + } + + location @silverstripe { + include fastcgi_params; + + # Defend against arbitrary PHP code execution + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + # More info: + # https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + fastcgi_param SCRIPT_FILENAME $document_root/sapphire/main.php; + fastcgi_param SCRIPT_NAME /sapphire/main.php; + fastcgi_param QUERY_STRING url=$uri&$args; + + fastcgi_pass unix:/var/run/php5-fpm.sock; + fastcgi_index index.php; + fastcgi_buffer_size 32k; + fastcgi_buffers 4 32k; + fastcgi_busy_buffers_size 64k; } -The above configuration will setup a new virtual host `yoursite.com` with rewrite rules suited for SilverStripe. The -location block at the bottom will pass all php scripts to the FastCGI-wrapper. +Here is the include file `htaccess`: + + # Don't serve up any .htaccess files + location ~ /\.ht { + deny all; + } + + # Deny access to silverstripe-cache + location ~ ^/silverstripe-cache { + deny all; + } + + # Don't execute scripts in the assets + location ^~ /assets/ { + try_files $uri $uri/ =404; + } + + # cms & sapphire .htaccess rules + location ~ ^/(cms|sapphire|mysite)/.*\.(php|php[345]|phtml|inc)$ { + deny all; + } + location ~ ^/(cms|sapphire)/silverstripe_version$ { + deny all; + } + location ~ ^/sapphire/.*(main|static-main|rpc|tiny_mce_gzip)\.php$ { + allow all; + } + +Here is the optional include file `ssl`: + + listen 443 ssl; + ssl_certificate server.crt; + ssl_certificate_key server.key; + ssl_session_timeout 5m; + ssl_protocols SSLv3 TLSv1; + ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP; + +The above configuration sets up a virtual host `example.com` with +rewrite rules suited for SilverStripe. The location block named +`@silverstripe` passes all php scripts to the FastCGI-wrapper via a Unix +socket. This example is from a site running Ubuntu with the php5-fpm +package. Now you can proceed with the SilverStripe installation normally. From 45c68d6821576eb694df6565c75c0bf5fb363ebf Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 12 Feb 2013 23:21:13 +0100 Subject: [PATCH 14/30] API Require ADMIN for ?showtemplate=1 --- core/SSViewer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/SSViewer.php b/core/SSViewer.php index af033bebc..d10f6f6cb 100755 --- a/core/SSViewer.php +++ b/core/SSViewer.php @@ -401,7 +401,7 @@ class SSViewer { } - if(isset($_GET['showtemplate']) && !Director::isLive()) { + if(isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) { $lines = file($cacheFile); echo "

Template: $cacheFile

"; echo "
";
@@ -680,7 +680,7 @@ class SSViewer_FromString extends SSViewer {
 		fwrite($fh, $template);
 		fclose($fh);
 
-		if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
+		if(isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) {
 			$lines = file($tmpFile);
 			echo "

Template: $tmpFile

"; echo "
";

From d969e29d000c75d3ce2b16c50949a79afdeb4bdd Mon Sep 17 00:00:00 2001
From: Ingo Schommer 
Date: Tue, 12 Feb 2013 23:23:18 +0100
Subject: [PATCH 15/30] API Require ADMIN for ?showtemplate=1

---
 docs/en/changelogs/3.0.4.md | 8 ++++++++
 view/SSViewer.php           | 2 +-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/docs/en/changelogs/3.0.4.md b/docs/en/changelogs/3.0.4.md
index fd9f55e1f..a9e5e4466 100644
--- a/docs/en/changelogs/3.0.4.md
+++ b/docs/en/changelogs/3.0.4.md
@@ -3,6 +3,14 @@
 ## Overview
 
  * Changed `dev/tests/setdb` and `dev/tests/startsession` from session to cookie storage.
+ * Require ADMIN permissions for `?showtemplate=1`
+
+## Details
+
+### Require ADMIN permissions for `?showtemplate=1`
+
+Avoids information leakage of compiled template data,
+which might expose some of the internal template logic.
 
 ## Upgrading
 
diff --git a/view/SSViewer.php b/view/SSViewer.php
index b3a315122..f1bb51b52 100644
--- a/view/SSViewer.php
+++ b/view/SSViewer.php
@@ -821,7 +821,7 @@ class SSViewer {
 	 * @return string - The result of executing the template
 	 */
 	protected function includeGeneratedTemplate($cacheFile, $item, $overlay, $underlay) {
-		if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
+		if(isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) {
 			$lines = file($cacheFile);
 			echo "

Template: $cacheFile

"; echo "
";

From 5d3ed12e20435212ccbd676571a4a83909c709fd Mon Sep 17 00:00:00 2001
From: Ingo Schommer 
Date: Fri, 15 Feb 2013 19:21:35 +0100
Subject: [PATCH 16/30] Nginx docs for denying composer file access (fixes
 #8011)

---
 docs/en/installation/nginx.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/docs/en/installation/nginx.md b/docs/en/installation/nginx.md
index c7ba04a50..f10f80739 100644
--- a/docs/en/installation/nginx.md
+++ b/docs/en/installation/nginx.md
@@ -62,6 +62,11 @@ Here is the include file `htaccess`:
 	location ~ ^/silverstripe-cache {
 		deny all;
 	}
+
+	# Deny access to composer
+	location ~ ^/(vendor|composer.json|composer.lock) {
+		deny all;
+	}
 	
 	# Don't execute scripts in the assets
 	location ^~ /assets/ {

From e21bd494621e3092edd893e3083ad5c8b16603f3 Mon Sep 17 00:00:00 2001
From: Ingo Schommer 
Date: Sun, 17 Feb 2013 20:57:53 +0100
Subject: [PATCH 17/30] BUG TimeField respects user choice (fixes #8260)

Regression from c969e04731a6443a4efbdcc6304edd4e88003e83.
Also fixes width to accommodate for widest common format:
"11:11:11 AM"
---
 admin/css/screen.css   | 26 +++++++++++++-------------
 admin/scss/_forms.scss |  2 +-
 forms/TimeField.php    |  2 +-
 3 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/admin/css/screen.css b/admin/css/screen.css
index 95437f7d0..263d028ab 100644
--- a/admin/css/screen.css
+++ b/admin/css/screen.css
@@ -129,8 +129,8 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif;
 .cms .ui-widget-header .ui-dialog-title { padding: 6px 0; text-shadow: #ced7dc 1px 1px 0; }
 .cms .ui-widget-header a.ui-dialog-titlebar-close { position: absolute; top: -8px; right: -15px; width: 30px; height: 30px; z-index: 100000; }
 .cms .ui-widget-header a.ui-state-hover { border-color: transparent; background: transparent; }
-.cms .ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-sb47394f892.png') 0 0 no-repeat; }
-.cms .ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-sb47394f892.png') 0 -102px no-repeat; width: 30px; height: 30px; }
+.cms .ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('sprites-32x32-sb47394f892.png') 0 0 no-repeat; }
+.cms .ui-widget-header .ui-icon-closethick { background: url('sprites-32x32-sb47394f892.png') 0 -102px no-repeat; width: 30px; height: 30px; }
 .cms .ui-state-hover { cursor: pointer; }
 .cms .ui-widget input, .cms .ui-widget select, .cms .ui-widget textarea, .cms .ui-widget button { color: #444444; font-size: 12px; font-family: Arial, sans-serif; }
 .cms .ui-accordion .ui-accordion-header { border-color: #c0c0c2; margin-bottom: 0; }
@@ -182,7 +182,7 @@ form.small .field input.text, form.small .field textarea, form.small .field sele
 .field .chzn-container-single .chzn-single div { width: 24px; }
 .field .chzn-container-single .chzn-single div b { background-position: 4px 0px; }
 .field input.month, .field input.day, .field input.year { width: 56px; }
-.field input.time { width: 64px; }
+.field input.time { width: 88px; }
 .field.remove-splitter { border-bottom: none; box-shadow: none; }
 
 /** ---------------------------------------------------- Buttons ---------------------------------------------------- */
@@ -593,7 +593,7 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai
 /** -------------------------------------------- Step labels -------------------------------------------- */
 .step-label > * { display: inline-block; vertical-align: top; }
 .step-label .flyout { height: 18px; font-size: 14px; font-weight: bold; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; background-color: #667980; padding: 4px 3px 4px 6px; text-align: center; text-shadow: none; color: #fff; }
-.step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-sb47394f892.png') 0 -40px no-repeat; margin-right: 4px; }
+.step-label .arrow { height: 26px; width: 10px; background: url('sprites-32x32-sb47394f892.png') 0 -40px no-repeat; margin-right: 4px; }
 .step-label .title { height: 18px; padding: 4px; }
 
 /** -------------------------------------------- Item Edit Form -------------------------------------------- */
@@ -644,10 +644,10 @@ form.import-form label.left { width: 250px; }
 /** -------------------------------------------- Buttons for FileUpload -------------------------------------------- */
 .ss-uploadfield-item-edit-all .ui-button-text { padding-right: 0; }
 
-.toggle-details-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -217px no-repeat; }
-.fileOverview .toggle-details-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -159px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; }
-.toggle-details-icon.opened { background: url('../images/sprites-32x32-sb47394f892.png') 0 -905px no-repeat; }
-.fileOverview .toggle-details-icon.opened { background: url('../images/sprites-32x32-sb47394f892.png') 0 -143px no-repeat; }
+.toggle-details-icon { background: url('sprites-32x32-sb47394f892.png') 0 -217px no-repeat; }
+.fileOverview .toggle-details-icon { background: url('sprites-32x32-sb47394f892.png') 0 -159px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; }
+.toggle-details-icon.opened { background: url('sprites-32x32-sb47394f892.png') 0 -905px no-repeat; }
+.fileOverview .toggle-details-icon.opened { background: url('sprites-32x32-sb47394f892.png') 0 -143px no-repeat; }
 
 /** This file defines the jstree base styling (see http://jstree.com), as well as any customizations (see bottom of file).  The styles are usually added through jstree.js on DOM load,  but we need it earlier in order to correctly display the uninitialized tree. */
 .cms .jstree ul, .TreeDropdownField .treedropdownfield-panel .jstree ul { display: block; margin: 0; padding: 0; background: none; list-style-type: none; }
@@ -774,7 +774,7 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; }
 .cms-logo span { font-weight: bold; font-size: 14px; line-height: 20px; padding: 2px 0; margin-left: 44px; }
 
 .cms-login-status { border-top: 1px solid #19435c; padding: 7px 4px; line-height: 16px; font-size: 11px; }
-.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 3px; background: url('../images/sprites-32x32-sb47394f892.png') 0 -76px no-repeat; text-indent: -9999em; }
+.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 3px; background: url('sprites-32x32-sb47394f892.png') 0 -76px no-repeat; text-indent: -9999em; }
 
 .cms-menu { z-index: 80; background: #b0bec7; width: 192px; -webkit-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; -moz-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; }
 .cms-menu a { text-decoration: none; }
@@ -798,12 +798,12 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; }
 .cms-menu-list li a .icon { display: inline-block; float: left; margin: 4px 10px 0 4px; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); opacity: 0.7; }
 .cms-menu-list li a .text { display: inline-block; float: left; }
 .cms-menu-list li a .toggle-children { display: inline-block; float: right; width: 20px; height: 100%; cursor: pointer; }
-.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('../images/sprites-32x32-sb47394f892.png') 0 -159px no-repeat; vertical-align: middle; }
-.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -143px no-repeat; }
+.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('sprites-32x32-sb47394f892.png') 0 -159px no-repeat; vertical-align: middle; }
+.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('sprites-32x32-sb47394f892.png') 0 -143px no-repeat; }
 .cms-menu-list li ul li a { border-top: 1px solid #b6c3cb; }
 .cms-menu-list li.current a { color: white; text-shadow: #1e5270 0 -1px 0; border-top: 1px solid #55a4d2; border-bottom: 1px solid #1e5270; background-color: #338dc1; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzMzOGRjMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzI4NzA5OSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #338dc1), color-stop(100%, #287099)); background-image: -webkit-linear-gradient(#338dc1, #287099); background-image: -moz-linear-gradient(#338dc1, #287099); background-image: -o-linear-gradient(#338dc1, #287099); background-image: linear-gradient(#338dc1, #287099); }
-.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -217px no-repeat; }
-.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -905px no-repeat; }
+.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('sprites-32x32-sb47394f892.png') 0 -217px no-repeat; }
+.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('sprites-32x32-sb47394f892.png') 0 -905px no-repeat; }
 .cms-menu-list li.current ul { border-top: none; display: block; }
 .cms-menu-list li.current li { background-color: #287099; }
 .cms-menu-list li.current li a { font-size: 11px; padding: 0 10px 0 40px; height: 32px; line-height: 32px; color: #e2f0f7; background: none; border-top: 1px solid #2f81b1; border-bottom: 1px solid #1e5270; }
diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss
index fe6ae890b..599ca480c 100644
--- a/admin/scss/_forms.scss
+++ b/admin/scss/_forms.scss
@@ -250,7 +250,7 @@ form.small .field, .field.small {
 	}
 
 	input.time { 
-		width: ($grid-x * 8); // smaller time field, since input is restricted
+		width: ($grid-x * 11); // smaller time field, since input is restricted
 	}
 	
 	/* Hides borders in settings/access. Activated from JS */
diff --git a/forms/TimeField.php b/forms/TimeField.php
index b1e5e427d..ac6986325 100644
--- a/forms/TimeField.php
+++ b/forms/TimeField.php
@@ -27,7 +27,7 @@ class TimeField extends TextField {
 	 * @var array
 	 */
 	static $default_config = array(
-		'timeformat' => 'HH:mm:ss',
+		'timeformat' => null,
 		'use_strtotime' => true,
 		'datavalueformat' => 'HH:mm:ss'
 	);

From ede381326b89c23624681d3d242dc092ec213071 Mon Sep 17 00:00:00 2001
From: Ingo Schommer 
Date: Sun, 17 Feb 2013 22:33:04 +0100
Subject: [PATCH 18/30] BUG Secure composer files from web access (fixes #8011)

Already applied to root .htaccess, but required for dynamically
generated file from installer as well. Also added upgrade instructions.
---
 dev/install/install.php5    |  2 ++
 docs/en/changelogs/3.0.4.md | 44 ++++++++++++++++++++++++++++++++++---
 2 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/dev/install/install.php5 b/dev/install/install.php5
index ae333d319..cc19ba4dc 100644
--- a/dev/install/install.php5
+++ b/dev/install/install.php5
@@ -1280,6 +1280,8 @@ ErrorDocument 500 /assets/error-500.html
 
 
 	RedirectMatch 403 /silverstripe-cache(/|$)
+	RedirectMatch 403 /vendor(/|$)
+	RedirectMatch 403 /composer\.(json|lock)
 
 
 
diff --git a/docs/en/changelogs/3.0.4.md b/docs/en/changelogs/3.0.4.md
index a9e5e4466..7021f5a6b 100644
--- a/docs/en/changelogs/3.0.4.md
+++ b/docs/en/changelogs/3.0.4.md
@@ -2,14 +2,52 @@
 
 ## Overview
 
+ * Security: Information leakage through web access on YAML configuration files
+ * Security: Information leakage through web access on composer files
+ * Security: Require ADMIN permissions for `?showtemplate=1`
  * Changed `dev/tests/setdb` and `dev/tests/startsession` from session to cookie storage.
- * Require ADMIN permissions for `?showtemplate=1`
 
 ## Details
 
-### Require ADMIN permissions for `?showtemplate=1`
+### Security: Prevent web access to YAML and composer files
 
-Avoids information leakage of compiled template data,
+Severity: Moderate
+
+Description: YAML files are used to configure the SilverStripe application
+since its 3.0 release. These files can contain sensitive values such as database
+and API credentials. By default, the installer still stores database credentials
+in `_config.php` files which are safe from web access. So this only concerns
+configuration values added in your own project, or a third party module.
+
+Resolution: Update your `.htaccess` file (for Apache), or your `web.config` file (for IIS)
+with the new files from the project root, and reapply any customizations you've made.
+Follow the [general upgrade instructions](/installation/upgrading).
+The [nginx installation instructions](/installation/nginx)
+have also been updated to reflect those changes.
+
+### Security: Information exposure through web access on composer files
+
+Severity: Low
+
+Description: [Composer](http://getcomposer.org) is a dependency management
+tool which can optionally be used to install SilverStripe. The `composer.json`
+and `composer.lock` files are required for its operation, so they are included 
+in the standard release since 3.0.2. These files contain information on the installed
+versions of core and thirdparty modules, which could be used to target specific
+versions of SilverStripe.
+
+Resolution: Update your `.htaccess` file (for Apache), or your `web.config` file (for IIS)
+with the new files from the project root, and reapply any customizations you've made.
+Follow the [general upgrade instructions](/installation/upgrading).
+The [nginx installation instructions](/installation/nginx)
+have also been updated to reflect those changes.
+
+
+### Security: Require ADMIN permissions for `?showtemplate=1`
+
+Severity: Low
+
+Description: Avoids information leakage of compiled template data,
 which might expose some of the internal template logic.
 
 ## Upgrading

From 7bb0bbff0e2eca8dac03b7d8287c44591394f18f Mon Sep 17 00:00:00 2001
From: Ingo Schommer 
Date: Fri, 4 Jan 2013 16:47:50 +0100
Subject: [PATCH 19/30] BUGFIX Fixed XSS in admin/security and "My Profile"
 forms

---
 forms/MemberDatetimeOptionsetField.php | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/forms/MemberDatetimeOptionsetField.php b/forms/MemberDatetimeOptionsetField.php
index d3ef113d7..7cdf9635c 100644
--- a/forms/MemberDatetimeOptionsetField.php
+++ b/forms/MemberDatetimeOptionsetField.php
@@ -35,17 +35,27 @@ class MemberDatetimeOptionsetField extends OptionsetField {
 		$value = ($this->value && !array_key_exists($this->value, $this->source)) ? $this->value : null;
 		$checked = ($value) ? " checked=\"checked\"" : '';
 		$options .= "
  • " - . sprintf("", - $itemID, $this->name, $checked) - . sprintf('', - $itemID, _t('MemberDatetimeOptionsetField.Custom', 'Custom')) - . sprintf("\n", $this->name, $value) - . sprintf("", - $this->Link() . '/validate'); + . sprintf( + "", + $itemID, $this->name, + $checked + ) + . sprintf( + '', + $itemID, _t('MemberDatetimeOptionsetField.Custom', 'Custom') + ) + . sprintf( + "\n", + $this->name, Convert::raw2xml($value) + ) + . sprintf( + "", + $this->Link() . '/validate' + ); $options .= ($value) ? sprintf( '(%s: "%s")', _t('MemberDatetimeOptionsetField.Preview', 'Preview'), - Zend_Date::now()->toString($value) + Convert::raw2xml(Zend_Date::now()->toString($value)) ) : ''; $options .= sprintf( '%s', From 604ede30a42df91571f27323defd2bc7dc0e7991 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 4 Jan 2013 17:29:24 +0100 Subject: [PATCH 20/30] BUGFIX Escape HTML in CMS status messages --- admin/javascript/LeftAndMain.js | 1 + 1 file changed, 1 insertion(+) diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index abbb58c2a..41f1960bd 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -963,6 +963,7 @@ jQuery.noConflict(); }(jQuery)); var statusMessage = function(text, type) { + text = $('
    ').text(text).html(); // Escape HTML entities in text jQuery.noticeAdd({text: text, type: type}); }; From f8bbc0a7265ca8da74bd1ffd073180a887dfbdeb Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 4 Jan 2013 18:09:39 +0100 Subject: [PATCH 21/30] BUGFIX Escape HTML in DropdownField and ListboxField Fixes reflected XSS in Group titles when using in group selections (e.g. in "New Member" form). --- templates/forms/DropdownField.ss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/forms/DropdownField.ss b/templates/forms/DropdownField.ss index f7df474e4..00d654e82 100644 --- a/templates/forms/DropdownField.ss +++ b/templates/forms/DropdownField.ss @@ -1,5 +1,5 @@ From 303352926be62c96ac0c1f636e74faa64cc92104 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 4 Jan 2013 18:21:26 +0100 Subject: [PATCH 22/30] 3.0.4 changelog update --- docs/en/changelogs/3.0.4.md | 46 ++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/en/changelogs/3.0.4.md b/docs/en/changelogs/3.0.4.md index 7021f5a6b..688bee870 100644 --- a/docs/en/changelogs/3.0.4.md +++ b/docs/en/changelogs/3.0.4.md @@ -5,11 +5,14 @@ * Security: Information leakage through web access on YAML configuration files * Security: Information leakage through web access on composer files * Security: Require ADMIN permissions for `?showtemplate=1` + * Security: Reflected XSS in custom date/time formats in admin/security + * Security: Stored XSS in the "New Group" dialog + * Security: Reflected XSS in CMS status messages * Changed `dev/tests/setdb` and `dev/tests/startsession` from session to cookie storage. ## Details -### Security: Prevent web access to YAML and composer files +### Security: Information exposure through web access on YAML configuration files Severity: Moderate @@ -52,6 +55,47 @@ which might expose some of the internal template logic. ## Upgrading +### Security: Reflected XSS in custom date/time formats in admin/security + +Severity: Low + +Prerequisite: An attacker must have access to the admin interface. + +Description: When creating a new user on the security page +(Security->New User) within the admin interface, the user input +is not properly validated and not encoded. A reflected XSS is +possible within the `DateFormat_custom` and `TimeFormat_custom` fields. + +Credits: Andreas Hunkeler (Compass Security AG, http://www.csnc.ch) + +### Security: Stored XSS in the "New Group" dialog + +Severity: Low + +Prerequisite: An attacker must have access to the admin interface. + +Description: There is a stored XSS vulnerability on the "group" tab on the +security page in the admin interface +(Security -> Groups -> New Group). It's possible to store a +XSS within the group name. Everywhere where these group names +are used, the XSS is executed. E.g. "New User" or "New Group". + +Credits: Andreas Hunkeler (Compass Security AG, http://www.csnc.ch) + +### Security: XSS in CMS status messages + +Severity: Low + +Prerequisite: An attacker must have access to the admin interface. + +Description: Any data returned to CMS status messages (Growl-style popovers on top right) +was not escaped, allowing XSS e.g. when publishing a page with +a specifically crafted "Title" field. + +Credits: Andreas Hunkeler (Compass Security AG, http://www.csnc.ch) + +### Misc + * If you are using `dev/tests/setdb` and `dev/tests/startsession`, you'll need to configure a secure token in order to encrypt the cookie value: Simply run `sake dev/generatesecuretoken` and add the resulting code to your `mysite/_config.php`. From f06ba70fc98bd58441a02889bfdb6849e6bd22bd Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 15 Jan 2013 00:34:05 +0100 Subject: [PATCH 23/30] BUG Undefined `$allowed_actions` overrides parent definitions, stricter handling of $allowed_actions on Extension Controller (and subclasses) failed to enforce $allowed_action restrictions on parent classes if a child class didn't have it explicitly defined. Controllers which are extended with $allowed_actions (through an Extension) now deny access to methods defined on the controller, unless this class also has them in its own $allowed_actions definition. --- control/RequestHandler.php | 27 +++-- docs/en/changelogs/3.0.4.md | 33 +++++- tests/control/ControllerTest.php | 165 ++++++++++++++++++++------ tests/control/RequestHandlingTest.php | 13 +- 4 files changed, 188 insertions(+), 50 deletions(-) diff --git a/control/RequestHandler.php b/control/RequestHandler.php index 38221aa75..c46403367 100644 --- a/control/RequestHandler.php +++ b/control/RequestHandler.php @@ -88,7 +88,7 @@ class RequestHandler extends ViewableData { * * * Form getters count as URL actions as well, and should be included in allowed_actions. - * Form actions on the other handed (first argument to {@link FormAction()} shoudl NOT be included, + * Form actions on the other handed (first argument to {@link FormAction()} should NOT be included, * these are handled separately through {@link Form->httpSubmission}. You can control access on form actions * either by conditionally removing {@link FormAction} in the form construction, * or by defining $allowed_actions in your {@link Form} class. @@ -321,21 +321,30 @@ class RequestHandler extends ViewableData { // If we get here an the action is 'index', then it hasn't been specified, which means that // it should be allowed. if($action == 'index' || empty($action)) return true; - - if($allowedActions === null || !$this->config()->get('allowed_actions', - Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES)) { - - // If no allowed_actions are provided, then we should only let through actions that aren't handled by + + // Get actions defined for this specific class + $relevantActions = null; + if($allowedActions !== null && method_exists($this, $action)) { + $r = new ReflectionClass(get_class($this)); + $m = $r->getMethod($actionOrigCasing); + $relevantActions = Object::uninherited_static( + $m->getDeclaringClass()->getName(), + 'allowed_actions' + ); + } + + // If no allowed_actions are provided on in the whole inheritance chain, + // or they aren't provided on the specific class... + if($allowedActions === null || $relevantActions === null) { + // ... only let through actions that aren't handled by // magic methods we test this by calling the unmagic method_exists. if(method_exists($this, $action)) { - // Disallow any methods which aren't defined on RequestHandler or subclasses - // (e.g. ViewableData->getSecurityID()) $r = new ReflectionClass(get_class($this)); if($r->hasMethod($actionOrigCasing)) { $m = $r->getMethod($actionOrigCasing); return ($m && is_subclass_of($m->getDeclaringClass()->getName(), 'RequestHandler')); } else { - throw new Exception("method_exists() true but ReflectionClass can't find method - PHP is b0kred"); + throw new Exception("method_exists() true but ReflectionClass can't find method"); } } else if(!$this->hasMethod($action)){ // Return true so that a template can handle this action diff --git a/docs/en/changelogs/3.0.4.md b/docs/en/changelogs/3.0.4.md index 688bee870..bbeee3bdc 100644 --- a/docs/en/changelogs/3.0.4.md +++ b/docs/en/changelogs/3.0.4.md @@ -2,16 +2,39 @@ ## Overview + * Security: Undefined or empty `$allowed_actions` overrides parent definitions * Security: Information leakage through web access on YAML configuration files * Security: Information leakage through web access on composer files * Security: Require ADMIN permissions for `?showtemplate=1` * Security: Reflected XSS in custom date/time formats in admin/security * Security: Stored XSS in the "New Group" dialog * Security: Reflected XSS in CMS status messages + * API: More restrictive `$allowed_actions` checks for `Controller` when used with `Extension` * Changed `dev/tests/setdb` and `dev/tests/startsession` from session to cookie storage. ## Details +### Security: Undefined or empty `$allowed_actions` overrides parent definitions + +Severity: Important + +Description: `Controller` (and subclasses) failed to enforce `$allowed_action` restrictions +on parent classes if a child class didn't have it explicitly defined, or it is set to an empty array. +Since this is the default configuration on `Page_Controller`, most SilverStripe installations +will be affected. + +Impact: Depends on the used controller code. For any method with public visibility, +the flaw can expose the return value of the method (unless it fails due to wrong arguments). +It can also lead to unauthorized or unintended execution of logic, e.g. modifying the +state of a database record. + +Fix: Apply 3.0.4 update. In addition, we strongly recommend to define `$allowed_actions` +on all controller classes to ensure the intentions are clearly communicated. +Read more about `$allowed_actions` in our "[controller](/topics/controller/#access-control)" +docs. + +Reporter: Zann St Pierre + ### Security: Information exposure through web access on YAML configuration files Severity: Moderate @@ -53,8 +76,6 @@ Severity: Low Description: Avoids information leakage of compiled template data, which might expose some of the internal template logic. -## Upgrading - ### Security: Reflected XSS in custom date/time formats in admin/security Severity: Low @@ -94,7 +115,13 @@ a specifically crafted "Title" field. Credits: Andreas Hunkeler (Compass Security AG, http://www.csnc.ch) -### Misc +### API: More restrictive `$allowed_actions` checks for `Controller` when used with `Extension` + +Controllers which are extended with `$allowed_actions` (through an `Extension`) +now deny access to methods defined on the controller, unless this class also has them in its own +`$allowed_actions` definition. + +## Upgrading * If you are using `dev/tests/setdb` and `dev/tests/startsession`, you'll need to configure a secure token in order to encrypt the cookie value: diff --git a/tests/control/ControllerTest.php b/tests/control/ControllerTest.php index f02fe50c4..a47bbba5a 100644 --- a/tests/control/ControllerTest.php +++ b/tests/control/ControllerTest.php @@ -31,54 +31,82 @@ class ControllerTest extends FunctionalTest { } public function testUndefinedActions() { - $response = Director::test('ControllerTest_UnsecuredController/undefinedaction'); + $response = Director::test('ControllerTest_AccessUnsecuredSubController/undefinedaction'); $this->assertEquals(404, $response->getStatusCode(), 'Undefined actions return a not found response.'); } public function testAllowedActions() { $adminUser = $this->objFromFixture('Member', 'admin'); - $response = $this->get("ControllerTest_SecuredController/methodaction"); - $this->assertEquals(200, $response->getStatusCode()); - - $response = $this->get("ControllerTest_SecuredController/stringaction"); - $this->assertEquals(404, $response->getStatusCode()); + $response = $this->get("ControllerTest_AccessBaseController/unsecuredaction"); + $this->assertEquals(200, $response->getStatusCode(), + 'Access granted on action without $allowed_actions on defining controller' + ); - $response = $this->get("ControllerTest_SecuredController/adminonly"); - $this->assertEquals(403, $response->getStatusCode()); - - $response = $this->get('ControllerTest_UnsecuredController/stringaction'); - $this->assertEquals(200, $response->getStatusCode(), - "test that a controller without a specified allowed_actions allows actions through" + $response = $this->get("ControllerTest_AccessBaseController/onlysecuredinsubclassaction"); + $this->assertEquals(200, $response->getStatusCode(), + 'Access granted on action without $allowed_actions on defining controller, ' . + 'even when action is secured in subclasses' ); - $response = $this->get("ControllerTest_FullSecuredController/index"); + $response = $this->get("ControllerTest_AccessSecuredController/onlysecuredinsubclassaction"); + $this->assertEquals(403, $response->getStatusCode(), + 'Access denied on action with $allowed_actions on defining controller, ' . + 'even if action is unsecured on parent class' + ); + + $response = $this->get("ControllerTest_AccessSecuredController/adminonly"); + $this->assertEquals(403, $response->getStatusCode(), + 'Access denied on action with $allowed_actions on defining controller, ' . + 'when action is not defined on any parent classes' + ); + + $response = $this->get("ControllerTest_AccessSecuredController/aDmiNOnlY"); + $this->assertEquals(403, $response->getStatusCode(), + 'Access denied on action with $allowed_actions on defining controller, ' . + 'regardless of capitalization' + ); + + // TODO Change this API + $response = $this->get('ControllerTest_AccessUnsecuredSubController/unsecuredaction'); + $this->assertEquals(200, $response->getStatusCode(), + "Controller without a specified allowed_actions allows its own actions through" + ); + + $response = $this->get('ControllerTest_AccessUnsecuredSubController/adminonly'); + $this->assertEquals(403, $response->getStatusCode(), + "Controller without a specified allowed_actions still disallows actions defined on parents" + ); + + $response = $this->get("ControllerTest_AccessAsteriskSecuredController/index"); $this->assertEquals(403, $response->getStatusCode(), "Actions can be globally disallowed by using asterisk (*) for index method" ); - $response = $this->get("ControllerTest_FullSecuredController/adminonly"); + $response = $this->get("ControllerTest_AccessAsteriskSecuredController/onlysecuredinsubclassaction"); $this->assertEquals(404, $response->getStatusCode(), - "Actions can be globally disallowed by using asterisk (*) instead of a method name" + "Actions can be globally disallowed by using asterisk (*) instead of a method name, " . + "in which case they'll be marked as 'not found'" ); - $response = $this->get("ControllerTest_FullSecuredController/unsecuredaction"); + $response = $this->get("ControllerTest_AccessAsteriskSecuredController/unsecuredaction"); $this->assertEquals(200, $response->getStatusCode(), "Actions can be overridden to be allowed if globally disallowed by using asterisk (*)" ); $this->session()->inst_set('loggedInAs', $adminUser->ID); - $response = $this->get("ControllerTest_SecuredController/adminonly"); + $response = $this->get("ControllerTest_AccessSecuredController/adminonly"); $this->assertEquals( 200, $response->getStatusCode(), "Permission codes are respected when set in \$allowed_actions" ); - $response = $this->get("ControllerTest_FullSecuredController/adminonly"); + $response = $this->get("ControllerTest_AccessUnsecuredSubController/adminonly"); $this->assertEquals(200, $response->getStatusCode(), "Actions can be globally disallowed by using asterisk (*) instead of a method name" ); + $this->session()->inst_set('loggedInAs', null); } @@ -227,48 +255,111 @@ class ControllerTest_Controller extends Controller implements TestOnly { } /** - * Controller with an $allowed_actions value + * Allowed actions flattened: + * - unsecuredaction: * + * - onlysecuredinsubclassaction: * + * - unsecuredinparentclassaction: * + * - unsecuredinsubclassaction: * */ -class ControllerTest_SecuredController extends Controller implements TestOnly { - static $allowed_actions = array( - "methodaction", - "adminonly" => "ADMIN", - ); - - public $Content = "default content"; - - public function methodaction() { - return array( - "Content" => "methodaction content" - ); - } - - public function stringaction() { - return "stringaction was called."; +class ControllerTest_AccessBaseController extends Controller implements TestOnly { + // Accessible by all + public function unsecuredaction() { + return 'unsecuredaction was called.'; } + // Accessible by all + public function onlysecuredinsubclassaction() { + return 'onlysecuredinsubclass was called.'; + } + + // Accessible by all + public function unsecuredinparentclassaction() { + return 'unsecuredinparentclassaction was called.'; + } + + // Accessible by all + public function unsecuredinsubclassaction() { + return 'unsecuredinsubclass was called.'; + } +} + +/** + * Allowed actions flattened: + * - unsecuredaction: * + * - onlysecuredinsubclassaction: ADMIN (parent: *) + * - unsecuredinparentclassaction: * + * - unsecuredinsubclassaction: (none) (parent: *) + * - adminonly: ADMIN + */ +class ControllerTest_AccessSecuredController extends ControllerTest_AccessBaseController implements TestOnly { + + static $allowed_actions = array( + "onlysecuredinsubclassaction" => 'ADMIN', + "adminonly" => "ADMIN", + ); + + // Accessible by ADMIN only + public function onlysecuredinsubclassaction() { + return 'onlysecuredinsubclass was called.'; + } + + // Not accessible, since overloaded but not mentioned in $allowed_actions + public function unsecuredinsubclassaction() { + return 'unsecuredinsubclass was called.'; + } + + // Accessible by all, since only defined in parent class + //public function unsecuredinparentclassaction() { + + // Accessible by ADMIN only public function adminonly() { return "You must be an admin!"; } } -class ControllerTest_FullSecuredController extends Controller implements TestOnly { +/** + * Allowed actions flattened: + * - unsecuredaction: * + * - onlysecuredinsubclassaction: ADMIN (parent: *) + * - unsecuredinparentclassaction: ADMIN (parent: *) + * - unsecuredinsubclassaction: (none) (parent: *) + * - adminonly: ADMIN (parent: ADMIN) + */ +class ControllerTest_AccessAsteriskSecuredController extends ControllerTest_AccessBaseController implements TestOnly { static $allowed_actions = array( "*" => "ADMIN", 'unsecuredaction' => true, ); + // Accessible by ADMIN only public function adminonly() { return "You must be an admin!"; } + // Accessible by all public function unsecuredaction() { return "Allowed for everybody"; } } -class ControllerTest_UnsecuredController extends ControllerTest_SecuredController implements TestOnly {} +/** + * Allowed actions flattened: + * - unsecuredaction: * + * - onlysecuredinsubclassaction: ADMIN (parent: *) + * - unsecuredinparentclassaction: * + * - unsecuredinsubclassaction: (none) (parent: *) + * - adminonly: ADMIN + */ +class ControllerTest_AccessUnsecuredSubController extends ControllerTest_AccessSecuredController implements TestOnly { + + // No $allowed_actions defined here + + // Accessible by ADMIN only, defined in parent class + public function onlysecuredinsubclassaction() { + return 'onlysecuredinsubclass was called.'; + } +} class ControllerTest_HasAction extends Controller { diff --git a/tests/control/RequestHandlingTest.php b/tests/control/RequestHandlingTest.php index 40fa57d22..698072d58 100644 --- a/tests/control/RequestHandlingTest.php +++ b/tests/control/RequestHandlingTest.php @@ -147,7 +147,7 @@ class RequestHandlingTest extends FunctionalTest { public function testMethodsOnParentClassesOfRequestHandlerDeclined() { $response = Director::test('testGoodBase1/getIterator'); - $this->assertEquals(403, $response->getStatusCode()); + $this->assertEquals(404, $response->getStatusCode()); } public function testFormActionsCanBypassAllowedActions() { @@ -280,6 +280,17 @@ Config::inst()->update('Director', 'rules', array( * Controller for the test */ class RequestHandlingTest_Controller extends Controller implements TestOnly { + + static $allowed_actions = array( + 'method', + 'legacymethod', + 'virtualfile', + 'TestForm', + 'throwexception', + 'throwresponseexception', + 'throwhttperror', + ); + static $url_handlers = array( // The double-slash is need here to ensure that '$Action//$ID/$OtherID' => "handleAction", From d51e0bc2ec7046cb319964bb84f2c882c553fa0d Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 28 Jan 2013 22:35:32 +0100 Subject: [PATCH 24/30] Improved docs on $allowed_actions Added section to "Controllers" and "Form" topics, added $allowed_actions definitions to all controller examples --- docs/en/howto/csv-import.md | 3 + docs/en/howto/simple-contact-form.md | 2 + docs/en/reference/execution-pipeline.md | 1 + docs/en/reference/grid-field.md | 2 + docs/en/reference/templates.md | 2 + docs/en/topics/controller.md | 99 ++++++++++++++++++++++++- docs/en/topics/form-validation.md | 1 + docs/en/topics/forms.md | 19 ++++- docs/en/topics/security.md | 4 +- docs/en/tutorials/3-forms.md | 2 + 10 files changed, 127 insertions(+), 8 deletions(-) diff --git a/docs/en/howto/csv-import.md b/docs/en/howto/csv-import.md index 5c8253397..b53ce3c02 100644 --- a/docs/en/howto/csv-import.md +++ b/docs/en/howto/csv-import.md @@ -68,6 +68,9 @@ You can have more customized logic and interface feedback through a custom contr :::php renderWith('MyTemplate'); } diff --git a/docs/en/reference/grid-field.md b/docs/en/reference/grid-field.md index 465786a1e..6d4aeb2ff 100644 --- a/docs/en/reference/grid-field.md +++ b/docs/en/reference/grid-field.md @@ -41,6 +41,8 @@ Here is an example where we display a basic gridfield with the default settings: :::php class GridController extends Page_Controller { + + static $allowed_actions = array('index'); public function index(SS_HTTPRequest $request) { $this->Content = $this->AllPages(); diff --git a/docs/en/reference/templates.md b/docs/en/reference/templates.md index 1fe556dde..81b3ca064 100644 --- a/docs/en/reference/templates.md +++ b/docs/en/reference/templates.md @@ -568,6 +568,8 @@ default if it exists and there is no action in the url parameters. :::php class MyPage_Controller extends Page_Controller { + + static $allowed_actions = array('index'); public function init(){ parent::init(); diff --git a/docs/en/topics/controller.md b/docs/en/topics/controller.md index 268e4aaf6..cfd800410 100644 --- a/docs/en/topics/controller.md +++ b/docs/en/topics/controller.md @@ -3,7 +3,7 @@ Base controller class. You will extend this to take granular control over the actions and url handling of aspects of your SilverStripe site. -## Example +## Usage The following example is for a simple `[api:Controller]` class. If you're using the cms module and looking at Page_Controller instances you won't need to setup @@ -15,11 +15,14 @@ your own routes since the cms module handles these routes. cheesefries ) +
    + SilverStripe automatically adds a URL routing entry based on the controller's class name, + so a `MyController` class is accessible through `http://yourdomain.com/MyController`. +
    + +## Access Control + +### Through $allowed_actions + +All public methods on a controller are accessible by their name through the `$Action` +part of the URL routing, so a `MyController->mymethod()` is accessible at +`http://yourdomain.com/MyController/mymethod`. This is not always desireable, +since methods can return internal information, or change state in a way +that's not intended to be used through a URL endpoint. + +SilverStripe strongly recommends securing your controllers +through defining a `$allowed_actions` array on the class, +which allows whitelisting of methods, as well as a concise +way to perform checks against permission codes or custom logic. + + :::php + class MyController extends Controller { + public static $allowed_actions = array( + // someaction can be accessed by anyone, any time + 'someaction', + // So can otheraction + 'otheraction' => true, + // restrictedaction can only be people with ADMIN privilege + 'restrictedaction' => 'ADMIN', + // complexaction can only be accessed if $this->canComplexAction() returns true + 'complexaction' '->canComplexAction' + ); + } + +There's a couple of rules guiding these checks: + + * Each controller is only responsible for access control on the methods it defines + * If a method on a parent class is overwritten, access control for it has to be redefined as well + * An action named "index" is whitelisted by default + * A wildcard (`*`) can be used to define access control for all methods (incl. methods on parent classes) + * Specific method entries in `$allowed_actions` overrule any `*` settings + * Methods returning forms also count as actions which need to be defined + * Form action methods (targets of `FormAction`) should NOT be included in `$allowed_actions`, + they're handled separately through the form routing (see the ["forms" topic](/topics/forms)) + * `$allowed_actions` can be defined on `Extension` classes applying to the controller. + + +If the permission check fails, SilverStripe will return a "403 Forbidden" HTTP status. + +### Through the action + +Each method responding to a URL can also implement custom permission checks, +e.g. to handle responses conditionally on the passed request data. + + :::php + class MyController extends Controller { + public static $allowed_actions = array('myaction'); + public function myaction($request) { + if(!$request->getVar('apikey')) { + return $this->httpError(403, 'No API key provided'); + } + + return 'valid'; + } + } + +Unless you transform the response later in the request processing, +it'll look pretty ugly to the user. Alternatively, you can use +`ErrorPage::response_for()` to return a more specialized layout. + +Note: This is recommended as an addition for `$allowed_actions`, in order to handle +more complex checks, rather than a replacement. + +### Through the init() method + +After checking for allowed_actions, each controller invokes its `init()` method, +which is typically used to set up common state in the controller, and +include JavaScript and CSS files in the output which are used for any action. +If an `init()` method returns a `SS_HTTPResponse` with either a 3xx or 4xx HTTP +status code, it'll abort execution. This behaviour can be used to implement +permission checks. + + :::php + class MyController extends Controller { + public static $allowed_actions = array(); + public function init() { + parent::init(); + if(!Permission::check('ADMIN')) return $this->httpError(403); + } + } ## URL Handling @@ -60,7 +153,7 @@ through `/fastfood/drivethrough/` to use the same order function. :::php class FastFood_Controller extends Controller { - + static $allowed_actions = array('drivethrough'); public static $url_handlers = array( 'drivethrough/$Action/$ID/$Name' => 'order' ); diff --git a/docs/en/topics/form-validation.md b/docs/en/topics/form-validation.md index ccd7906f9..a99a3b421 100644 --- a/docs/en/topics/form-validation.md +++ b/docs/en/topics/form-validation.md @@ -49,6 +49,7 @@ Example: Validate postcodes based on the selected country (on the controller). :::php class MyController extends Controller { + static $allowed_actions = array('Form'); public function Form() { return Form::create($this, 'Form', new FieldList( diff --git a/docs/en/topics/forms.md b/docs/en/topics/forms.md index 5844912e8..5ac3189f3 100644 --- a/docs/en/topics/forms.md +++ b/docs/en/topics/forms.md @@ -25,9 +25,7 @@ Forms start at the controller. Here is an simple example on how to set up a form :::php class Page_Controller extends ContentController { - public static $allowed_actions = array( - 'HelloForm', - ); + public static $allowed_actions = array('HelloForm'); // Template method public function HelloForm() { @@ -41,11 +39,24 @@ Forms start at the controller. Here is an simple example on how to set up a form return $form; } - public function doSayHello(array $data, Form $form) { + public function doSayHello($data, Form $form) { // Do something with $data return $this->render(); } } + +The name of the form ("HelloForm") is passed into the `Form` +constructor as a second argument. It needs to match the method name. + +Since forms need a URL, the `HelloForm()` method needs to be handled +like any other controller action. In order to whitelist its access through +URLs, we add it to the `$allowed_actions` array. +Form actions ("doSayHello") on the other hand should NOT be included here, +these are handled separately through `Form->httpSubmission()`. +You can control access on form actions either by conditionally removing +a `FormAction` from the form construction, +or by defining `$allowed_actions` in your own `Form` class +(more information in the ["controllers" topic](/topics/controllers)). **Page.ss** diff --git a/docs/en/topics/security.md b/docs/en/topics/security.md index fff3e792d..efd48b67f 100644 --- a/docs/en/topics/security.md +++ b/docs/en/topics/security.md @@ -79,6 +79,7 @@ Example: :::php class MyController extends Controller { + static $allowed_actions = array('myurlaction'); public function myurlaction($RAW_urlParams) { $SQL_urlParams = Convert::raw2sql($RAW_urlParams); // works recursively on an array $objs = Player::get()->where("Name = '{$SQL_data[OtherID]}'"); @@ -93,7 +94,6 @@ This means if you've got a chain of functions passing data through, escaping sho :::php class MyController extends Controller { /** - * @param array $RAW_data All names in an indexed array (not SQL-safe) */ public function saveAllNames($RAW_data) { @@ -220,6 +220,7 @@ PHP: :::php class MyController extends Controller { + static $allowed_actions = array('search'); public function search($request) { $htmlTitle = '

    Your results for:' . Convert::raw2xml($request->getVar('Query')) . '

    '; return $this->customise(array( @@ -249,6 +250,7 @@ PHP: :::php class MyController extends Controller { + static $allowed_actions = array('search'); public function search($request) { $rssRelativeLink = "/rss?Query=" . urlencode($_REQUEST['query']) . "&sortOrder=asc"; $rssLink = Controller::join_links($this->Link(), $rssRelativeLink); diff --git a/docs/en/tutorials/3-forms.md b/docs/en/tutorials/3-forms.md index 8f8435660..635014f1c 100644 --- a/docs/en/tutorials/3-forms.md +++ b/docs/en/tutorials/3-forms.md @@ -21,6 +21,8 @@ The poll we will be creating on our homepage will ask the user for their name an :::php class HomePage_Controller extends Page_Controller { + static $allowed_actions = array('BrowserPollForm'); + // ... public function BrowserPollForm() { From 30096ee73091074474a5831c0f60e02e65b72a30 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Sun, 6 Jan 2013 21:20:02 +0100 Subject: [PATCH 25/30] BUGFIX Keep Member.PasswordEncryption setting on empty passwords This will prevent empty passwords to set the encryption to 'none', which in turn will store any subsequent password changes in cleartext. Reproduceable e.g. with ConfirmedPasswordField and setCanBeEmpty(true). --- security/Security.php | 13 ++----------- tests/security/MemberTest.php | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/security/Security.php b/security/Security.php index 9bc78516f..936e74b4f 100644 --- a/security/Security.php +++ b/security/Security.php @@ -829,17 +829,8 @@ class Security extends Controller { * @see set_password_encryption_algorithm() */ public static function encrypt_password($password, $salt = null, $algorithm = null, $member = null) { - if( - // if the password is empty, don't encrypt - strlen(trim($password)) == 0 - // if no algorithm is provided and no default is set, don't encrypt - || (!$algorithm) - ) { - $algorithm = 'none'; - } else { - // Fall back to the default encryption algorithm - if(!$algorithm) $algorithm = self::$encryptionAlgorithm; - } + // Fall back to the default encryption algorithm + if(!$algorithm) $algorithm = self::$encryptionAlgorithm; $e = PasswordEncryptor::create_for_algorithm($algorithm); diff --git a/tests/security/MemberTest.php b/tests/security/MemberTest.php index 4879fa764..0d56aa122 100644 --- a/tests/security/MemberTest.php +++ b/tests/security/MemberTest.php @@ -114,6 +114,23 @@ class MemberTest extends FunctionalTest { Security::set_password_encryption_algorithm($origAlgo); } + + public function testKeepsEncryptionOnEmptyPasswords() { + $member = new Member(); + $member->Password = 'mypassword'; + $member->PasswordEncryption = 'sha1_v2.4'; + $member->write(); + + $member->Password = ''; + $member->write(); + + $this->assertEquals( + $member->PasswordEncryption, + 'sha1_v2.4' + ); + $result = $member->checkPassword(''); + $this->assertTrue($result->valid()); + } public function testSetPassword() { $member = $this->objFromFixture('Member', 'test'); From d3d0b21e80c0af1ac59a9d566ef097a37f7548d3 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 18 Feb 2013 01:17:30 +0100 Subject: [PATCH 26/30] Updated translations --- lang/nl.yml | 22 +++++++++++----------- lang/uk.yml | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lang/nl.yml b/lang/nl.yml index c93eeb4da..fbb6dcd4f 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -19,7 +19,7 @@ nl: DROPAREA: 'Hierheen slepen' EDITALL: 'Alle bewerken' EDITANDORGANIZE: 'Bewerk en beheer' - EDITINFO: 'Edit files' + EDITINFO: 'Bewerk alle bestanden' FILES: Bestanden FROMCOMPUTER: 'Selecteer bestand op computer' FROMCOMPUTERINFO: 'Upload from your computer' @@ -175,16 +175,16 @@ nl: XlsType: 'Excel document' ZipType: 'ZIP bestand' FileIFrameField: - ATTACH: 'Attach {type}' - ATTACHONCESAVED: '{type}s can be attached once you have saved the record for the first time.' - ATTACHONCESAVED2: 'Files can be attached once you have saved the record for the first time.' - DELETE: 'Delete {type}' + ATTACH: 'Toevoegen {type}' + ATTACHONCESAVED: '{type}en kunnen worden toegevoegd na het voor het eerst opslaan.' + ATTACHONCESAVED2: 'Bestanden kunnen worden toegevoegd na het voor het eerst opslaan.' + DELETE: 'Verwijderen {type}' DISALLOWEDFILETYPE: 'Dit type bestand mag niet worden opgeslagen' FILE: Bestand FROMCOMPUTER: 'Vanaf computer' FROMFILESTORE: 'Vanaf de website''s bestandsopslag' NOSOURCE: 'Selecteer een bron bestand om toe te voegen' - REPLACE: 'Replace {type}' + REPLACE: 'Vervang {type}' FileIFrameField_iframe.ss: TITLE: 'Afbeelding uploaden' Filesystem: @@ -232,7 +232,7 @@ nl: DeletePermissionsFailure: 'Onvoldoende rechten om te verwijderen' GridFieldDetailForm: CancelBtn: Annuleren - Create: Create + Create: Creëren Delete: Verwijder DeletePermissionsFailure: 'No delete permissions' Deleted: '%s %s verwijderd' @@ -334,7 +334,7 @@ nl: VersionUnknown: onbekend LeftAndMain_Menu.ss: Hello: Hi - LOGOUT: 'Log out' + LOGOUT: Uitloggen LoginAttempt: Email: 'Email adres ' IP: 'IP Adres' @@ -410,7 +410,7 @@ nl: MemberImportForm: Help1: '

    Importeer leden in CSV formaat (Kommagescheiden bestandsformaat). Toon geavanceerd gebruik

    ' Help2: '

    Advanced usage

    • Allowed columns: %s
    • Existing users are matched by their unique Code property, and updated with any new values from the imported file.
    • Groups can be assigned by the Groups column. Groups are identified by their Code property, multiple groups can be separated by comma. Existing group memberships are not cleared.
    ' - ResultCreated: 'Created {count} members' + ResultCreated: '{count} leden aangemaakt' ResultDeleted: '%d leden verwijderd' ResultNone: 'Geen wijzingen' ResultUpdated: 'Updated {count} members' @@ -551,13 +551,13 @@ nl: ATTACHFILE: 'Voeg een bestand toe' ATTACHFILES: 'Voeg bestanden toe' AttachFile: 'Voeg bestanden toe' - DELETE: 'Delete from files' + DELETE: 'Volledig verwijderen' DELETEINFO: 'Verwijder dit bestand uit bestandsopslag van de website.' DOEDIT: Bewaar DROPFILE: 'Bestand hiernaar toe slepen' DROPFILES: 'Sleep hier je bestanden' Dimensions: Dimensions - EDIT: Edit + EDIT: Bewerken EDITINFO: 'Bewerk dit bestand' FIELDNOTSET: 'Bestandsinformatie niet gevonden' FROMCOMPUTER: 'Vanaf computer' diff --git a/lang/uk.yml b/lang/uk.yml index 894373877..767d0641b 100644 --- a/lang/uk.yml +++ b/lang/uk.yml @@ -77,7 +77,7 @@ uk: CHANGEPASSWORDTEXT2: 'Тепер Ви можете використовувати наступні дані для входу:' EMAIL: Email HELLO: Привіт - PASSWORD: Password + PASSWORD: Пароль CheckboxField: - 'False' - 'True' @@ -240,7 +240,7 @@ uk: Saved: 'Saved %s %s' GridFieldItemEditView.ss: null Group: - AddRole: 'Add a role for this group' + AddRole: 'Додати роль до цієї групи' Code: 'Код групи' DefaultGroupTitleAdministrators: Адміністратори DefaultGroupTitleContentAuthors: 'Content Authors' @@ -250,7 +250,7 @@ uk: NoRoles: 'Ролі не знайдені' PLURALNAME: Groups Parent: 'Батьківська група' - RolesAddEditLink: 'Manage roles' + RolesAddEditLink: 'Керувати ролями' SINGULARNAME: Group Sort: 'Порядок сортування' has_many_Permissions: Права @@ -501,7 +501,7 @@ uk: EDITPERMISSIONS_HELP: 'Ability to edit Permissions and IP Addresses for a group. Requires the "Access to ''Security'' section" permission.' GROUPNAME: 'Назва групи' IMPORTGROUPS: 'Імпортувати групи' - IMPORTUSERS: 'Import users' + IMPORTUSERS: 'Імпортувати користувачів' MEMBERS: Члени MENUTITLE: Security MemberListCaution: 'Caution: Removing members from this list will remove them from all groups and the database' From 62987139d421919d29941609d09830e53a097aa4 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 18 Feb 2013 00:45:27 +0100 Subject: [PATCH 27/30] Updated changelog --- docs/en/changelogs/3.0.4.md | 2 +- docs/en/changelogs/index.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/changelogs/3.0.4.md b/docs/en/changelogs/3.0.4.md index bbeee3bdc..2a4d227f5 100644 --- a/docs/en/changelogs/3.0.4.md +++ b/docs/en/changelogs/3.0.4.md @@ -1,4 +1,4 @@ -# 3.0.4 +# 3.0.4 (2013-02-19) ## Overview diff --git a/docs/en/changelogs/index.md b/docs/en/changelogs/index.md index b8bc32e77..fd2227bd6 100644 --- a/docs/en/changelogs/index.md +++ b/docs/en/changelogs/index.md @@ -9,6 +9,8 @@ For information on how to upgrade to newer versions consult the [upgrading](/ins ## Stable Releases + * [3.0.4](3.0.4) - 19 February 2013 + * [3.0.3](3.0.3) - 26 November 2012 * [3.0.2](3.0.2) - 17 September 2012 * [3.0.1](3.0.1) - 31 July 2012 * [3.0.0](3.0.0) - 28 June 2012 From eafafb31e34aca78762f6f78b62ed5725ac331d2 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 18 Feb 2013 01:28:17 +0100 Subject: [PATCH 28/30] Fixed screen.css (wrong compilation) --- admin/css/screen.css | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index 263d028ab..8381bd5ca 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -129,8 +129,8 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif; .cms .ui-widget-header .ui-dialog-title { padding: 6px 0; text-shadow: #ced7dc 1px 1px 0; } .cms .ui-widget-header a.ui-dialog-titlebar-close { position: absolute; top: -8px; right: -15px; width: 30px; height: 30px; z-index: 100000; } .cms .ui-widget-header a.ui-state-hover { border-color: transparent; background: transparent; } -.cms .ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('sprites-32x32-sb47394f892.png') 0 0 no-repeat; } -.cms .ui-widget-header .ui-icon-closethick { background: url('sprites-32x32-sb47394f892.png') 0 -102px no-repeat; width: 30px; height: 30px; } +.cms .ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-sb47394f892.png') 0 0 no-repeat; } +.cms .ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-sb47394f892.png') 0 -102px no-repeat; width: 30px; height: 30px; } .cms .ui-state-hover { cursor: pointer; } .cms .ui-widget input, .cms .ui-widget select, .cms .ui-widget textarea, .cms .ui-widget button { color: #444444; font-size: 12px; font-family: Arial, sans-serif; } .cms .ui-accordion .ui-accordion-header { border-color: #c0c0c2; margin-bottom: 0; } @@ -593,7 +593,7 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai /** -------------------------------------------- Step labels -------------------------------------------- */ .step-label > * { display: inline-block; vertical-align: top; } .step-label .flyout { height: 18px; font-size: 14px; font-weight: bold; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; background-color: #667980; padding: 4px 3px 4px 6px; text-align: center; text-shadow: none; color: #fff; } -.step-label .arrow { height: 26px; width: 10px; background: url('sprites-32x32-sb47394f892.png') 0 -40px no-repeat; margin-right: 4px; } +.step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-sb47394f892.png') 0 -40px no-repeat; margin-right: 4px; } .step-label .title { height: 18px; padding: 4px; } /** -------------------------------------------- Item Edit Form -------------------------------------------- */ @@ -644,10 +644,10 @@ form.import-form label.left { width: 250px; } /** -------------------------------------------- Buttons for FileUpload -------------------------------------------- */ .ss-uploadfield-item-edit-all .ui-button-text { padding-right: 0; } -.toggle-details-icon { background: url('sprites-32x32-sb47394f892.png') 0 -217px no-repeat; } -.fileOverview .toggle-details-icon { background: url('sprites-32x32-sb47394f892.png') 0 -159px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; } -.toggle-details-icon.opened { background: url('sprites-32x32-sb47394f892.png') 0 -905px no-repeat; } -.fileOverview .toggle-details-icon.opened { background: url('sprites-32x32-sb47394f892.png') 0 -143px no-repeat; } +.toggle-details-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -217px no-repeat; } +.fileOverview .toggle-details-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -159px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; } +.toggle-details-icon.opened { background: url('../images/sprites-32x32-sb47394f892.png') 0 -905px no-repeat; } +.fileOverview .toggle-details-icon.opened { background: url('../images/sprites-32x32-sb47394f892.png') 0 -143px no-repeat; } /** This file defines the jstree base styling (see http://jstree.com), as well as any customizations (see bottom of file). The styles are usually added through jstree.js on DOM load, but we need it earlier in order to correctly display the uninitialized tree. */ .cms .jstree ul, .TreeDropdownField .treedropdownfield-panel .jstree ul { display: block; margin: 0; padding: 0; background: none; list-style-type: none; } @@ -774,7 +774,7 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; } .cms-logo span { font-weight: bold; font-size: 14px; line-height: 20px; padding: 2px 0; margin-left: 44px; } .cms-login-status { border-top: 1px solid #19435c; padding: 7px 4px; line-height: 16px; font-size: 11px; } -.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 3px; background: url('sprites-32x32-sb47394f892.png') 0 -76px no-repeat; text-indent: -9999em; } +.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 3px; background: url('../images/sprites-32x32-sb47394f892.png') 0 -76px no-repeat; text-indent: -9999em; } .cms-menu { z-index: 80; background: #b0bec7; width: 192px; -webkit-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; -moz-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; } .cms-menu a { text-decoration: none; } @@ -798,12 +798,12 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; } .cms-menu-list li a .icon { display: inline-block; float: left; margin: 4px 10px 0 4px; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); opacity: 0.7; } .cms-menu-list li a .text { display: inline-block; float: left; } .cms-menu-list li a .toggle-children { display: inline-block; float: right; width: 20px; height: 100%; cursor: pointer; } -.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('sprites-32x32-sb47394f892.png') 0 -159px no-repeat; vertical-align: middle; } -.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('sprites-32x32-sb47394f892.png') 0 -143px no-repeat; } +.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('../images/sprites-32x32-sb47394f892.png') 0 -159px no-repeat; vertical-align: middle; } +.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -143px no-repeat; } .cms-menu-list li ul li a { border-top: 1px solid #b6c3cb; } .cms-menu-list li.current a { color: white; text-shadow: #1e5270 0 -1px 0; border-top: 1px solid #55a4d2; border-bottom: 1px solid #1e5270; background-color: #338dc1; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzMzOGRjMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzI4NzA5OSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #338dc1), color-stop(100%, #287099)); background-image: -webkit-linear-gradient(#338dc1, #287099); background-image: -moz-linear-gradient(#338dc1, #287099); background-image: -o-linear-gradient(#338dc1, #287099); background-image: linear-gradient(#338dc1, #287099); } -.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('sprites-32x32-sb47394f892.png') 0 -217px no-repeat; } -.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('sprites-32x32-sb47394f892.png') 0 -905px no-repeat; } +.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -217px no-repeat; } +.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sb47394f892.png') 0 -905px no-repeat; } .cms-menu-list li.current ul { border-top: none; display: block; } .cms-menu-list li.current li { background-color: #287099; } .cms-menu-list li.current li a { font-size: 11px; padding: 0 10px 0 40px; height: 32px; line-height: 32px; color: #e2f0f7; background: none; border-top: 1px solid #2f81b1; border-bottom: 1px solid #1e5270; } From ad9f26a00f0dc9415447dbea810449b15634a5f7 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 18 Feb 2013 01:29:30 +0100 Subject: [PATCH 29/30] Updated changelog --- docs/en/changelogs/3.0.4.md | 140 +++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/docs/en/changelogs/3.0.4.md b/docs/en/changelogs/3.0.4.md index 2a4d227f5..88d80945e 100644 --- a/docs/en/changelogs/3.0.4.md +++ b/docs/en/changelogs/3.0.4.md @@ -126,4 +126,142 @@ now deny access to methods defined on the controller, unless this class also has * If you are using `dev/tests/setdb` and `dev/tests/startsession`, you'll need to configure a secure token in order to encrypt the cookie value: Simply run `sake dev/generatesecuretoken` and add the resulting code to your `mysite/_config.php`. - Note that this functionality now requires the PHP `mcrypt` extension. \ No newline at end of file + Note that this functionality now requires the PHP `mcrypt` extension. + +## Changelog + +### API Changes + + * 2013-02-15 [2352317](https://github.com/silverstripe/silverstripe-installer/commit/2352317) Filter composer files in IIS and Apache rules (fixes #8011) (Ingo Schommer) + * 2013-02-12 [d969e29](https://github.com/silverstripe/sapphire/commit/d969e29) Require ADMIN for ?showtemplate=1 (Ingo Schommer) + * 2013-02-12 [45c68d6](https://github.com/silverstripe/sapphire/commit/45c68d6) Require ADMIN for ?showtemplate=1 (Ingo Schommer) + * 2013-01-23 [c69381c](https://github.com/silverstripe/sapphire/commit/c69381c) Remove Content-Length setting from HTTPResponse (fixes #8010) (Ingo Schommer) + * 2012-12-11 [10d447e](https://github.com/silverstripe/silverstripe-installer/commit/10d447e) Removed 'make getallmodules', use composer instead (Ingo Schommer) + * 2012-12-10 [efa9ff9](https://github.com/silverstripe/sapphire/commit/efa9ff9) Queries added by DataList::addInnerJoin() and DataList::leftJoin() come after the base joins, not before. (stojg) + * 2012-12-06 [c6b1d4a](https://github.com/silverstripe/sapphire/commit/c6b1d4a) Storing alternative DB name in cookie rather than session (Ingo Schommer) + * 2012-11-29 [f49f1ff](https://github.com/silverstripe/sapphire/commit/f49f1ff) Rename Transliterator to SS_Transliterator to remove conflict with Intl extension (Simon Welsh) + * 2012-11-19 [96e56a8](https://github.com/silverstripe/silverstripe-installer/commit/96e56a8) Removed 'new-project' command (Ingo Schommer) + * 2012-11-19 [8b68644](https://github.com/silverstripe/silverstripe-installer/commit/8b68644) Moved build tools to new silverstripe-buildtools module (Ingo Schommer) + * 2012-11-09 [22095da](https://github.com/silverstripe/sapphire/commit/22095da) Hash autologin tokens before storing in the database. (Mateusz Uzdowski) + * 2011-03-16 [d8bfc0b](https://github.com/silverstripe/sapphire/commit/d8bfc0b) Added Security::set_login_url() so that you can define an alternative log-in page if you have made one yourself. (Sam Minnee) + * 2011-03-16 [f546979](https://github.com/silverstripe/sapphire/commit/f546979) Add a PermissionFailureException that can be thrown to trigger a log-in. (Sam Minnee) + +### Features and Enhancements + + * 2013-02-05 [f0621cd](https://github.com/silverstripe/sapphire/commit/f0621cd) Added ability to query size of Varchar (Daniel Hensby) + * 2013-02-01 [119d8aa](https://github.com/silverstripe/silverstripe-cms/commit/119d8aa) Do not display SilverStripeNavigator_CMSLink when in a LeftAndMain extension not just CMSMain extensions (UndefinedOffset) + * 2013-01-11 [5b450f7](https://github.com/silverstripe/sapphire/commit/5b450f7) Added replaceExistingFile setting for UploadField. (Sam Minnee) + * 2013-01-11 [cc7318f](https://github.com/silverstripe/sapphire/commit/cc7318f) Added canAttachExisting config option for UploadField. (Sam Minnee) + * 2013-01-10 [5e6f5f9](https://github.com/silverstripe/sapphire/commit/5e6f5f9) Allow configuration of send_all_emails_to, ccs_all_emails_to, and bcc_all_emails_to via the config system. (Sam Minnee) + * 2013-01-09 [67c5db3](https://github.com/silverstripe/sapphire/commit/67c5db3) Global default config for UploadField (Ingo Schommer) + * 2013-01-09 [2dfd427](https://github.com/silverstripe/sapphire/commit/2dfd427) Restrict upload abilities in UploadField (Ingo Schommer) + * 2013-01-07 [abbee41](https://github.com/silverstripe/sapphire/commit/abbee41) Add ReadonlyField::setIncludeHiddenField() (Sam Minnee) + * 2012-12-07 [e8fbfc0](https://github.com/silverstripe/sapphire/commit/e8fbfc0) FixtureFactory separated out from YamlFixture (Ingo Schommer) + * 2012-11-15 [e07ae20](https://github.com/silverstripe/silverstripe-installer/commit/e07ae20) Added "phing phpunit" target (Ingo Schommer) + * 2012-11-09 [32f829d](https://github.com/silverstripe/sapphire/commit/32f829d) Support for Behat tests, and initial set of tests (Ingo Schommer) + * 2012-11-09 [9841d5b](https://github.com/silverstripe/silverstripe-cms/commit/9841d5b) Added Behat tests (Ingo Schommer) + * 2012-10-24 [ea2dc9d](https://github.com/silverstripe/sapphire/commit/ea2dc9d) Add ability to change URL for SS logo in CMS Menu (Loz Calver) + * 2012-10-16 [c4dde90](https://github.com/silverstripe/sapphire/commit/c4dde90) Allow hashes to be passed as ArrayList items; the will be turned into ArrayData objects. (Sam Minnee) + * 2011-09-29 [2916f20](https://github.com/silverstripe/sapphire/commit/2916f20) Improve HTTP caching logic to automatically disable caching for requests that use the session. (Hamish Friedlander) + * 2011-03-02 [c3a3ff4](https://github.com/silverstripe/sapphire/commit/c3a3ff4) Added Email::send_all_emails_from() setting. (Sam Minnee) + +### Bugfixes + + * 2013-02-18 [eafafb3](https://github.com/silverstripe/sapphire/commit/eafafb3) ed screen.css (wrong compilation) (Ingo Schommer) + * 2013-02-17 [ede3813](https://github.com/silverstripe/sapphire/commit/ede3813) Secure composer files from web access (fixes #8011) (Ingo Schommer) + * 2013-02-17 [e21bd49](https://github.com/silverstripe/sapphire/commit/e21bd49) TimeField respects user choice (fixes #8260) (Ingo Schommer) + * 2013-02-07 [79eacb2](https://github.com/silverstripe/sapphire/commit/79eacb2) Group->canEdit() correct non-admin checks (fixes #8250) (Ingo Schommer) + * 2013-02-04 [857d8bb](https://github.com/silverstripe/sapphire/commit/857d8bb) Don't escape values on TreeDropdownField readonly views (Ingo Schommer) + * 2013-02-04 [97fbfd3](https://github.com/silverstripe/silverstripe-cms/commit/97fbfd3) Respect escaping rules on readonly fields in CMS history view (Ingo Schommer) + * 2013-02-04 [e56a78b](https://github.com/silverstripe/silverstripe-cms/commit/e56a78b) updateCMSFields not accepting var by reference (Michael Andrewartha) + * 2013-02-04 [866bb07](https://github.com/silverstripe/sapphire/commit/866bb07) validate doesn't take var by reference (Michael Andrewartha) + * 2013-02-04 [1960df8](https://github.com/silverstripe/sapphire/commit/1960df8) Strict error warnings on DataExtension (Michael Andrewartha) + * 2013-01-31 [1bb1090](https://github.com/silverstripe/sapphire/commit/1bb1090) Node updates in IE without non-object error (Ingo Schommer) + * 2013-01-30 [c9f728f](https://github.com/silverstripe/sapphire/commit/c9f728f) Only check the remember token if a user exists (Simon Welsh) + * 2013-01-24 [9ac3cde](https://github.com/silverstripe/sapphire/commit/9ac3cde) typo in templates/Controller.ss (Nicolaas) + * 2013-01-24 [f574979](https://github.com/silverstripe/sapphire/commit/f574979) Exception handling and email notification mechanism now correctly considers the stacktrace as provided by the exceptionHandler function, instead of attempting to perform a debug_backtrace further down the reporting chain (which ends up generating an unnecessarily nested stacktrace). Debug was cleaned up so that errorHandler and exceptionHandler both act consistently. As a result, the LogErrorEmailFormatter class could be simplified. (Damian Mooyman) + * 2013-01-23 [45eb0f9](https://github.com/silverstripe/sapphire/commit/45eb0f9) PHPUnit latest not working with composer installed builds (Hamish Friedlander) + * 2013-01-21 [5d37d55](https://github.com/silverstripe/sapphire/commit/5d37d55) Form session message clearing regression (Ingo Schommer) + * 2013-01-18 [0c9b216](https://github.com/silverstripe/sapphire/commit/0c9b216) Escape the -f argument passed to mail() (Sam Minnee) + * 2013-01-17 [e74ec57](https://github.com/silverstripe/sapphire/commit/e74ec57) Permission checkbox display on members (fixes #8193) (Ingo Schommer) + * 2013-01-15 [a70df3e](https://github.com/silverstripe/sapphire/commit/a70df3e) PaginatedList deprecated method was calling non-existent method (Jeremy Thomerson) + * 2013-01-15 [014f541](https://github.com/silverstripe/sapphire/commit/014f541) Regression in Form->clearMessage() (fixes #8186) (Ingo Schommer) + * 2013-01-15 [64d3a3d](https://github.com/silverstripe/sapphire/commit/64d3a3d) Don't double unescape URLs in history.js (fixes #8170) (Ingo Schommer) + * 2013-01-15 [420c639](https://github.com/silverstripe/sapphire/commit/420c639) Properly show link for showing and hiding class spec in model admin (jean) + * 2013-01-15 [f06ba70](https://github.com/silverstripe/sapphire/commit/f06ba70) Undefined `$allowed_actions` overrides parent definitions, stricter handling of $allowed_actions on Extension (Ingo Schommer) + * 2013-01-11 [e020c7b](https://github.com/silverstripe/sapphire/commit/e020c7b) doSave() and doDelete() should use translated singular name (uniun) + * 2013-01-11 [212c4f1](https://github.com/silverstripe/sapphire/commit/212c4f1) ed UploadField regression from 4da1af9c3 (Ingo Schommer) + * 2013-01-11 [f8758ba](https://github.com/silverstripe/sapphire/commit/f8758ba) Fixed margins so that margin is displayed between preview images and their title. (Sam Minnee) + * 2013-01-11 [2fdd9a3](https://github.com/silverstripe/sapphire/commit/2fdd9a3) Allow images attached to UploadFields to be unlinked without File::canEdit() or File::canDelete() permission. (Sam Minnee) + * 2013-01-11 [f4efaee](https://github.com/silverstripe/sapphire/commit/f4efaee) Fix DataObject::get_one() when the classname is passed with improper casing. (Sam Minnee) + * 2013-01-06 [30096ee](https://github.com/silverstripe/sapphire/commit/30096ee) Keep Member.PasswordEncryption setting on empty passwords (Ingo Schommer) + * 2013-01-04 [f8bbc0a](https://github.com/silverstripe/sapphire/commit/f8bbc0a) Escape HTML in DropdownField and ListboxField (Ingo Schommer) + * 2013-01-04 [604ede3](https://github.com/silverstripe/sapphire/commit/604ede3) Escape HTML in CMS status messages (Ingo Schommer) + * 2013-01-04 [7bb0bbf](https://github.com/silverstripe/sapphire/commit/7bb0bbf) Fixed XSS in admin/security and "My Profile" forms (Ingo Schommer) + * 2012-12-21 [f0f83b2](https://github.com/silverstripe/sapphire/commit/f0f83b2) Graceful handling of sprintf with too few params in i18n::_t() (Ingo Schommer) + * 2012-12-19 [22efd38](https://github.com/silverstripe/sapphire/commit/22efd38) Calling DataObject::relField() on a object with an empty relation list (Stig Lindqvist) + * 2012-12-18 [07fb756](https://github.com/silverstripe/sapphire/commit/07fb756) edge case in which uninitialized buttons are being destroyed. (unclecheese) + * 2012-12-18 [6aba24b](https://github.com/silverstripe/sapphire/commit/6aba24b) removeRequiredField() should use array_splice() instead of unset() (uniun) + * 2012-12-18 [d5a1c3d](https://github.com/silverstripe/sapphire/commit/d5a1c3d) SS has problems handling + in URLs. Filter them out. (Mateusz Uzdowski) + * 2012-12-14 [55b611d](https://github.com/silverstripe/sapphire/commit/55b611d) Hardcoded project name in include_by_locale() (uniun) + * 2012-12-14 [f41f307](https://github.com/silverstripe/sapphire/commit/f41f307) ed spacing (Ingo Schommer) + * 2012-12-13 [d42c004](https://github.com/silverstripe/silverstripe-cms/commit/d42c004) Fixed pagination functionality on root assets folder (Niklas Forsdahl) + * 2012-12-12 [639cc02](https://github.com/silverstripe/sapphire/commit/639cc02) Fix insert media form inserting images from other UploadFields (fixes #8051) (Loz Calver) + * 2012-12-11 [f431b35](https://github.com/silverstripe/sapphire/commit/f431b35) Confirmed Password Field now copies attributes to child fields. (Justin Martin) + * 2012-12-07 [4f63f91](https://github.com/silverstripe/sapphire/commit/4f63f91) Fixed issue with convertServiceProperty (Marcus Nyeholt) + * 2012-12-06 [9152387](https://github.com/silverstripe/sapphire/commit/9152387) ed GridField button alignment (regression from a9cbea34) (Ingo Schommer) + * 2012-12-06 [1a4eaaa](https://github.com/silverstripe/sapphire/commit/1a4eaaa) Ensure has length before using string index access. (Simon Elvery) + * 2012-12-05 [205ee42](https://github.com/silverstripe/sapphire/commit/205ee42) Make sure a message is set on ValidationException objects. (Simon Elvery) + * 2012-12-04 [4c525fe](https://github.com/silverstripe/silverstripe-cms/commit/4c525fe) ed composer.json dependencies (Ingo Schommer) + * 2012-12-05 [c0751df](https://github.com/silverstripe/silverstripe-cms/commit/c0751df) Remove handwritten SQL and use the ORM. (Mateusz Uzdowski) + * 2012-12-04 [3be9499](https://github.com/silverstripe/sapphire/commit/3be9499) ed HTML syntax in config-form.html (Ingo Schommer) + * 2012-12-04 [0be51a9](https://github.com/silverstripe/sapphire/commit/0be51a9) Fix ModelAdmin search (fixes #8052) (Ingo Schommer) + * 2012-12-04 [f86bd97](https://github.com/silverstripe/sapphire/commit/f86bd97) ed DateTest timezone settings (Ingo Schommer) + * 2012-12-04 [bf67679](https://github.com/silverstripe/sapphire/commit/bf67679) typo in email docs. (Will Rossiter) + * 2012-12-04 [4cb81da](https://github.com/silverstripe/sapphire/commit/4cb81da) typo with setTemplate() rss docs. (Will Rossiter) + * 2012-12-04 [1a4b245](https://github.com/silverstripe/sapphire/commit/1a4b245) Fix rewriteHashlinks in TabSet (Marcus Nyeholt) + * 2012-12-04 [3478813](https://github.com/silverstripe/sapphire/commit/3478813) Rewrite hashlinks failing on empty a tags (Marcus Nyeholt) + * 2012-12-04 [449cce9](https://github.com/silverstripe/sapphire/commit/449cce9) ing .htaccess to ignore rewriting PHP files directly (Sean Harvey) + * 2012-12-04 [d74da7b](https://github.com/silverstripe/silverstripe-installer/commit/d74da7b) ing .htaccess to ignore rewriting PHP files directly (Sean Harvey) + * 2012-12-01 [9106e41](https://github.com/silverstripe/sapphire/commit/9106e41) related to ModelAdmin links (Simon Welsh) + * 2012-11-26 [40a1a35](https://github.com/silverstripe/silverstripe-cms/commit/40a1a35) Namespaces for CmsFormsContext and CmsUiContext are wrong (Kirk Mayo) + * 2012-11-23 [4310718](https://github.com/silverstripe/sapphire/commit/4310718) Insert Media, inserts all previous media (fixes #7545) (UndefinedOffset) + * 2012-11-23 [47e14f5](https://github.com/silverstripe/silverstripe-cms/commit/47e14f5) ed $TRAVIS_BRANCH env var usage in before_script (Ingo Schommer) + * 2012-11-23 [aa72425](https://github.com/silverstripe/sapphire/commit/aa72425) ed PHPUnit assertions for incomplete tests in core (Ingo Schommer) + * 2012-11-23 [453d04e](https://github.com/silverstripe/sapphire/commit/453d04e) Reset DataObject caches in SapphireTest->resetDBSchema() (Ingo Schommer) + * 2012-11-23 [a3cd7dd](https://github.com/silverstripe/sapphire/commit/a3cd7dd) Force SapphireTest schema reset for extension changes (Ingo Schommer) + * 2012-11-22 [ecd921c](https://github.com/silverstripe/sapphire/commit/ecd921c) ed glitches in the sample composer.json shown in the docs (Sam Minnee) + * 2012-11-21 [6eb597a](https://github.com/silverstripe/silverstripe-cms/commit/6eb597a) ed travis.yml paths (Ingo Schommer) + * 2012-11-21 [41aec54](https://github.com/silverstripe/silverstripe-cms/commit/41aec54) Consistently use FormResponse in CMS JavaScript (fixes #8036) (Ingo Schommer) + * 2012-11-20 [8f89aa9](https://github.com/silverstripe/sapphire/commit/8f89aa9) only call filemtime if file exists (Sander van Dragt) + * 2012-11-19 [65fe8e5](https://github.com/silverstripe/silverstripe-cms/commit/65fe8e5) ed line length (Ingo Schommer) + * 2012-11-16 [76c63fe](https://github.com/silverstripe/sapphire/commit/76c63fe) Fixed issue with SQLQuery::lastRow crashing on empty set. Added test cases for lastRow and firstRow. (Damian Mooyman) + * 2012-11-15 [c6fcb08](https://github.com/silverstripe/sapphire/commit/c6fcb08) Video embed from Add Media Feature no longer works (open #8033) (stojg) + * 2012-11-14 [91e48b8](https://github.com/silverstripe/silverstripe-cms/commit/91e48b8) Provide fallback text for translations. (Simon Elvery) + * 2012-11-12 [2657a27](https://github.com/silverstripe/sapphire/commit/2657a27) Adjust the handler to jQuery UI 1.9 API change. (Mateusz Uzdowski) + * 2012-11-12 [b6017a7](https://github.com/silverstripe/sapphire/commit/b6017a7) ArrayList now discards keys of the array passed in and keeps the numerically indexed array sequential. This fixes FirstLast and EvenOdd in templates, and makes ArrayList more consistent, as several methods already discarded the keys. (Andrew O'Neil) + * 2012-11-12 [d58b23d](https://github.com/silverstripe/silverstripe-cms/commit/d58b23d) AssetAdmin filter array indices (fixes #8014) (Kirk Mayo) + * 2012-11-09 [434759c](https://github.com/silverstripe/sapphire/commit/434759c) Correct redirection URL on deletion in GridFieldDetailForm (Ingo Schommer) + * 2012-11-09 [a2501ad](https://github.com/silverstripe/silverstripe-installer/commit/a2501ad) ed bootstrap.php path in phpunit.xml.dist (Ingo Schommer) + * 2012-11-09 [f0f5dcb](https://github.com/silverstripe/sapphire/commit/f0f5dcb) ed mediaform urls in modeladmin (Tim Klein) + * 2012-11-08 [f976ed9](https://github.com/silverstripe/silverstripe-installer/commit/f976ed9) ed composer.json regression from 2.4 merge (Ingo Schommer) + * 2012-11-08 [6882635](https://github.com/silverstripe/sapphire/commit/6882635) Fixing non-object on file upload (Sean Harvey) + * 2012-11-08 [6a69a2f](https://github.com/silverstripe/silverstripe-cms/commit/6a69a2f) Ensure required lang and css are loaded when using SiteTreeURLSegmentField (Simon Elvery) + * 2012-11-07 [fdcd7a2](https://github.com/silverstripe/sapphire/commit/fdcd7a2) ing performance of DataObject::custom_database_fields() (Sean Harvey) + * 2012-11-06 [abba77a](https://github.com/silverstripe/silverstripe-cms/commit/abba77a) es required for jQuery UI 1.9 (Ingo Schommer) + * 2012-11-06 [bcee252](https://github.com/silverstripe/sapphire/commit/bcee252) ed sapphiredocs references (Ingo Schommer) + * 2012-02-09 [c048a01](https://github.com/silverstripe/sapphire/commit/c048a01) Avoid infinite redirection when logging out and when showing a custom login page after displaying the draft version of a page. (jean) + * 2011-12-12 [1e1df8c](https://github.com/silverstripe/sapphire/commit/1e1df8c) Improved detection of empty HTMLText fields. (Sam Minnee) + * 2011-09-30 [f41a7d8](https://github.com/silverstripe/sapphire/commit/f41a7d8) Fix issue with not being able to log out on Chrome when caching enabled because of Chrome bug (Hamish Friedlander) + * 2011-09-01 [9a2ba48](https://github.com/silverstripe/sapphire/commit/9a2ba48) Made CSRF-error wording friendlier. (Sam Minnee) + * 2011-08-31 [729bcc9](https://github.com/silverstripe/sapphire/commit/729bcc9) Don't clear form messages unless forTemplate() is actually called. BUGFIX: Clear session-stored form data as well as form error message. (Sam Minnee) + * 2011-08-18 [5f9348b](https://github.com/silverstripe/sapphire/commit/5f9348b) Ensure that Security views respect redirections triggered by Page_Controller::init() (Sam Minnee) + * 2011-07-07 [b114aa2](https://github.com/silverstripe/sapphire/commit/b114aa2) Added X-Forwarded-Protocol and User-Agent to Vary header. (Sam Minnee) + * 2011-05-26 [55f3ec1](https://github.com/silverstripe/sapphire/commit/55f3ec1) Added error message fields to default search form (Jean-Fabien) + * 2011-05-23 [7026a48](https://github.com/silverstripe/sapphire/commit/7026a48) for date manipulation use the SS_Datetime::now, otherwise it does not respect the mock date. (Mateusz Uzdowski) + * 2011-05-21 [b7a1db7](https://github.com/silverstripe/sapphire/commit/b7a1db7) Set up the test mailer before loading the fixture, in case fixture-creation causes emails to be generated. (Sam Minnee) + * 2011-04-29 [47e037e](https://github.com/silverstripe/sapphire/commit/47e037e) Removed notice-level error after forms w/ required fields are made readonly. (Sam Minnee) + * 2011-04-20 [33a1fc7](https://github.com/silverstripe/sapphire/commit/33a1fc7) Fixed operation of inlined images in Mailer, when no inlined images actually attached. (Carlos Barberis) + * 2011-04-18 [f8206d1](https://github.com/silverstripe/sapphire/commit/f8206d1) Prevent notice-level error in Session code when non-array is turned into an array. (Sam Minnee) + * 2011-03-15 [6fcbad1](https://github.com/silverstripe/sapphire/commit/6fcbad1) Updated SilverStripe error handler so that log_errors still works. (Sam Minnee) + * 2011-03-11 [82988d4](https://github.com/silverstripe/sapphire/commit/82988d4) Better error message when 401 response is corrupted. (Sam Minnee) \ No newline at end of file From 37b8034462c254f578e9bc07c65745109548aa4b Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 18 Feb 2013 01:34:51 +0100 Subject: [PATCH 30/30] Fixed changelog --- docs/en/changelogs/3.0.4.md | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/docs/en/changelogs/3.0.4.md b/docs/en/changelogs/3.0.4.md index 88d80945e..ced2806cc 100644 --- a/docs/en/changelogs/3.0.4.md +++ b/docs/en/changelogs/3.0.4.md @@ -167,7 +167,6 @@ now deny access to methods defined on the controller, unless this class also has ### Bugfixes - * 2013-02-18 [eafafb3](https://github.com/silverstripe/sapphire/commit/eafafb3) ed screen.css (wrong compilation) (Ingo Schommer) * 2013-02-17 [ede3813](https://github.com/silverstripe/sapphire/commit/ede3813) Secure composer files from web access (fixes #8011) (Ingo Schommer) * 2013-02-17 [e21bd49](https://github.com/silverstripe/sapphire/commit/e21bd49) TimeField respects user choice (fixes #8260) (Ingo Schommer) * 2013-02-07 [79eacb2](https://github.com/silverstripe/sapphire/commit/79eacb2) Group->canEdit() correct non-admin checks (fixes #8250) (Ingo Schommer) @@ -178,7 +177,6 @@ now deny access to methods defined on the controller, unless this class also has * 2013-02-04 [1960df8](https://github.com/silverstripe/sapphire/commit/1960df8) Strict error warnings on DataExtension (Michael Andrewartha) * 2013-01-31 [1bb1090](https://github.com/silverstripe/sapphire/commit/1bb1090) Node updates in IE without non-object error (Ingo Schommer) * 2013-01-30 [c9f728f](https://github.com/silverstripe/sapphire/commit/c9f728f) Only check the remember token if a user exists (Simon Welsh) - * 2013-01-24 [9ac3cde](https://github.com/silverstripe/sapphire/commit/9ac3cde) typo in templates/Controller.ss (Nicolaas) * 2013-01-24 [f574979](https://github.com/silverstripe/sapphire/commit/f574979) Exception handling and email notification mechanism now correctly considers the stacktrace as provided by the exceptionHandler function, instead of attempting to perform a debug_backtrace further down the reporting chain (which ends up generating an unnecessarily nested stacktrace). Debug was cleaned up so that errorHandler and exceptionHandler both act consistently. As a result, the LogErrorEmailFormatter class could be simplified. (Damian Mooyman) * 2013-01-23 [45eb0f9](https://github.com/silverstripe/sapphire/commit/45eb0f9) PHPUnit latest not working with composer installed builds (Hamish Friedlander) * 2013-01-21 [5d37d55](https://github.com/silverstripe/sapphire/commit/5d37d55) Form session message clearing regression (Ingo Schommer) @@ -190,7 +188,6 @@ now deny access to methods defined on the controller, unless this class also has * 2013-01-15 [420c639](https://github.com/silverstripe/sapphire/commit/420c639) Properly show link for showing and hiding class spec in model admin (jean) * 2013-01-15 [f06ba70](https://github.com/silverstripe/sapphire/commit/f06ba70) Undefined `$allowed_actions` overrides parent definitions, stricter handling of $allowed_actions on Extension (Ingo Schommer) * 2013-01-11 [e020c7b](https://github.com/silverstripe/sapphire/commit/e020c7b) doSave() and doDelete() should use translated singular name (uniun) - * 2013-01-11 [212c4f1](https://github.com/silverstripe/sapphire/commit/212c4f1) ed UploadField regression from 4da1af9c3 (Ingo Schommer) * 2013-01-11 [f8758ba](https://github.com/silverstripe/sapphire/commit/f8758ba) Fixed margins so that margin is displayed between preview images and their title. (Sam Minnee) * 2013-01-11 [2fdd9a3](https://github.com/silverstripe/sapphire/commit/2fdd9a3) Allow images attached to UploadFields to be unlinked without File::canEdit() or File::canDelete() permission. (Sam Minnee) * 2013-01-11 [f4efaee](https://github.com/silverstripe/sapphire/commit/f4efaee) Fix DataObject::get_one() when the classname is passed with improper casing. (Sam Minnee) @@ -200,41 +197,25 @@ now deny access to methods defined on the controller, unless this class also has * 2013-01-04 [7bb0bbf](https://github.com/silverstripe/sapphire/commit/7bb0bbf) Fixed XSS in admin/security and "My Profile" forms (Ingo Schommer) * 2012-12-21 [f0f83b2](https://github.com/silverstripe/sapphire/commit/f0f83b2) Graceful handling of sprintf with too few params in i18n::_t() (Ingo Schommer) * 2012-12-19 [22efd38](https://github.com/silverstripe/sapphire/commit/22efd38) Calling DataObject::relField() on a object with an empty relation list (Stig Lindqvist) - * 2012-12-18 [07fb756](https://github.com/silverstripe/sapphire/commit/07fb756) edge case in which uninitialized buttons are being destroyed. (unclecheese) * 2012-12-18 [6aba24b](https://github.com/silverstripe/sapphire/commit/6aba24b) removeRequiredField() should use array_splice() instead of unset() (uniun) * 2012-12-18 [d5a1c3d](https://github.com/silverstripe/sapphire/commit/d5a1c3d) SS has problems handling + in URLs. Filter them out. (Mateusz Uzdowski) * 2012-12-14 [55b611d](https://github.com/silverstripe/sapphire/commit/55b611d) Hardcoded project name in include_by_locale() (uniun) - * 2012-12-14 [f41f307](https://github.com/silverstripe/sapphire/commit/f41f307) ed spacing (Ingo Schommer) * 2012-12-13 [d42c004](https://github.com/silverstripe/silverstripe-cms/commit/d42c004) Fixed pagination functionality on root assets folder (Niklas Forsdahl) * 2012-12-12 [639cc02](https://github.com/silverstripe/sapphire/commit/639cc02) Fix insert media form inserting images from other UploadFields (fixes #8051) (Loz Calver) * 2012-12-11 [f431b35](https://github.com/silverstripe/sapphire/commit/f431b35) Confirmed Password Field now copies attributes to child fields. (Justin Martin) * 2012-12-07 [4f63f91](https://github.com/silverstripe/sapphire/commit/4f63f91) Fixed issue with convertServiceProperty (Marcus Nyeholt) - * 2012-12-06 [9152387](https://github.com/silverstripe/sapphire/commit/9152387) ed GridField button alignment (regression from a9cbea34) (Ingo Schommer) * 2012-12-06 [1a4eaaa](https://github.com/silverstripe/sapphire/commit/1a4eaaa) Ensure has length before using string index access. (Simon Elvery) * 2012-12-05 [205ee42](https://github.com/silverstripe/sapphire/commit/205ee42) Make sure a message is set on ValidationException objects. (Simon Elvery) - * 2012-12-04 [4c525fe](https://github.com/silverstripe/silverstripe-cms/commit/4c525fe) ed composer.json dependencies (Ingo Schommer) * 2012-12-05 [c0751df](https://github.com/silverstripe/silverstripe-cms/commit/c0751df) Remove handwritten SQL and use the ORM. (Mateusz Uzdowski) - * 2012-12-04 [3be9499](https://github.com/silverstripe/sapphire/commit/3be9499) ed HTML syntax in config-form.html (Ingo Schommer) * 2012-12-04 [0be51a9](https://github.com/silverstripe/sapphire/commit/0be51a9) Fix ModelAdmin search (fixes #8052) (Ingo Schommer) - * 2012-12-04 [f86bd97](https://github.com/silverstripe/sapphire/commit/f86bd97) ed DateTest timezone settings (Ingo Schommer) - * 2012-12-04 [bf67679](https://github.com/silverstripe/sapphire/commit/bf67679) typo in email docs. (Will Rossiter) - * 2012-12-04 [4cb81da](https://github.com/silverstripe/sapphire/commit/4cb81da) typo with setTemplate() rss docs. (Will Rossiter) * 2012-12-04 [1a4b245](https://github.com/silverstripe/sapphire/commit/1a4b245) Fix rewriteHashlinks in TabSet (Marcus Nyeholt) * 2012-12-04 [3478813](https://github.com/silverstripe/sapphire/commit/3478813) Rewrite hashlinks failing on empty a tags (Marcus Nyeholt) - * 2012-12-04 [449cce9](https://github.com/silverstripe/sapphire/commit/449cce9) ing .htaccess to ignore rewriting PHP files directly (Sean Harvey) - * 2012-12-04 [d74da7b](https://github.com/silverstripe/silverstripe-installer/commit/d74da7b) ing .htaccess to ignore rewriting PHP files directly (Sean Harvey) - * 2012-12-01 [9106e41](https://github.com/silverstripe/sapphire/commit/9106e41) related to ModelAdmin links (Simon Welsh) * 2012-11-26 [40a1a35](https://github.com/silverstripe/silverstripe-cms/commit/40a1a35) Namespaces for CmsFormsContext and CmsUiContext are wrong (Kirk Mayo) * 2012-11-23 [4310718](https://github.com/silverstripe/sapphire/commit/4310718) Insert Media, inserts all previous media (fixes #7545) (UndefinedOffset) - * 2012-11-23 [47e14f5](https://github.com/silverstripe/silverstripe-cms/commit/47e14f5) ed $TRAVIS_BRANCH env var usage in before_script (Ingo Schommer) - * 2012-11-23 [aa72425](https://github.com/silverstripe/sapphire/commit/aa72425) ed PHPUnit assertions for incomplete tests in core (Ingo Schommer) * 2012-11-23 [453d04e](https://github.com/silverstripe/sapphire/commit/453d04e) Reset DataObject caches in SapphireTest->resetDBSchema() (Ingo Schommer) * 2012-11-23 [a3cd7dd](https://github.com/silverstripe/sapphire/commit/a3cd7dd) Force SapphireTest schema reset for extension changes (Ingo Schommer) - * 2012-11-22 [ecd921c](https://github.com/silverstripe/sapphire/commit/ecd921c) ed glitches in the sample composer.json shown in the docs (Sam Minnee) - * 2012-11-21 [6eb597a](https://github.com/silverstripe/silverstripe-cms/commit/6eb597a) ed travis.yml paths (Ingo Schommer) * 2012-11-21 [41aec54](https://github.com/silverstripe/silverstripe-cms/commit/41aec54) Consistently use FormResponse in CMS JavaScript (fixes #8036) (Ingo Schommer) * 2012-11-20 [8f89aa9](https://github.com/silverstripe/sapphire/commit/8f89aa9) only call filemtime if file exists (Sander van Dragt) - * 2012-11-19 [65fe8e5](https://github.com/silverstripe/silverstripe-cms/commit/65fe8e5) ed line length (Ingo Schommer) * 2012-11-16 [76c63fe](https://github.com/silverstripe/sapphire/commit/76c63fe) Fixed issue with SQLQuery::lastRow crashing on empty set. Added test cases for lastRow and firstRow. (Damian Mooyman) * 2012-11-15 [c6fcb08](https://github.com/silverstripe/sapphire/commit/c6fcb08) Video embed from Add Media Feature no longer works (open #8033) (stojg) * 2012-11-14 [91e48b8](https://github.com/silverstripe/silverstripe-cms/commit/91e48b8) Provide fallback text for translations. (Simon Elvery) @@ -242,14 +223,8 @@ now deny access to methods defined on the controller, unless this class also has * 2012-11-12 [b6017a7](https://github.com/silverstripe/sapphire/commit/b6017a7) ArrayList now discards keys of the array passed in and keeps the numerically indexed array sequential. This fixes FirstLast and EvenOdd in templates, and makes ArrayList more consistent, as several methods already discarded the keys. (Andrew O'Neil) * 2012-11-12 [d58b23d](https://github.com/silverstripe/silverstripe-cms/commit/d58b23d) AssetAdmin filter array indices (fixes #8014) (Kirk Mayo) * 2012-11-09 [434759c](https://github.com/silverstripe/sapphire/commit/434759c) Correct redirection URL on deletion in GridFieldDetailForm (Ingo Schommer) - * 2012-11-09 [a2501ad](https://github.com/silverstripe/silverstripe-installer/commit/a2501ad) ed bootstrap.php path in phpunit.xml.dist (Ingo Schommer) - * 2012-11-09 [f0f5dcb](https://github.com/silverstripe/sapphire/commit/f0f5dcb) ed mediaform urls in modeladmin (Tim Klein) - * 2012-11-08 [f976ed9](https://github.com/silverstripe/silverstripe-installer/commit/f976ed9) ed composer.json regression from 2.4 merge (Ingo Schommer) * 2012-11-08 [6882635](https://github.com/silverstripe/sapphire/commit/6882635) Fixing non-object on file upload (Sean Harvey) * 2012-11-08 [6a69a2f](https://github.com/silverstripe/silverstripe-cms/commit/6a69a2f) Ensure required lang and css are loaded when using SiteTreeURLSegmentField (Simon Elvery) - * 2012-11-07 [fdcd7a2](https://github.com/silverstripe/sapphire/commit/fdcd7a2) ing performance of DataObject::custom_database_fields() (Sean Harvey) - * 2012-11-06 [abba77a](https://github.com/silverstripe/silverstripe-cms/commit/abba77a) es required for jQuery UI 1.9 (Ingo Schommer) - * 2012-11-06 [bcee252](https://github.com/silverstripe/sapphire/commit/bcee252) ed sapphiredocs references (Ingo Schommer) * 2012-02-09 [c048a01](https://github.com/silverstripe/sapphire/commit/c048a01) Avoid infinite redirection when logging out and when showing a custom login page after displaying the draft version of a page. (jean) * 2011-12-12 [1e1df8c](https://github.com/silverstripe/sapphire/commit/1e1df8c) Improved detection of empty HTMLText fields. (Sam Minnee) * 2011-09-30 [f41a7d8](https://github.com/silverstripe/sapphire/commit/f41a7d8) Fix issue with not being able to log out on Chrome when caching enabled because of Chrome bug (Hamish Friedlander)