diff --git a/control/Director.php b/control/Director.php index 22e73f71f..d67b276fb 100644 --- a/control/Director.php +++ b/control/Director.php @@ -413,7 +413,12 @@ class Director implements TemplateGlobalProvider { /** * Turns the given URL into an absolute URL. - * @todo Document how relativeToSiteBase works + * By default non-site root relative urls will be evaluated relative to the current request. + * + * @param string $url URL To transform to absolute + * @param bool $relativeToSiteBase Flag indicating if non-site root relative urls should be + * evaluated relative to the site BaseURL instead of the current url. + * @return string The fully qualified URL */ public static function absoluteURL($url, $relativeToSiteBase = false) { if(!isset($_SERVER['REQUEST_URI'])) return false; diff --git a/control/HTTPResponse.php b/control/HTTPResponse.php index f1556a1c9..8b4cd9412 100644 --- a/control/HTTPResponse.php +++ b/control/HTTPResponse.php @@ -44,6 +44,7 @@ class SS_HTTPResponse { 416 => 'Request Range Not Satisfiable', 417 => 'Expectation Failed', 422 => 'Unprocessable Entity', + 429 => 'Too Many Requests', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', @@ -234,7 +235,7 @@ class SS_HTTPResponse { } if(in_array($this->statusCode, self::$redirect_codes) && headers_sent($file, $line)) { - $url = (string)$this->headers['Location']; + $url = Director::absoluteURL($this->headers['Location'], true); $urlATT = Convert::raw2htmlatt($url); $urlJS = Convert::raw2js($url); echo diff --git a/dev/FunctionalTest.php b/dev/FunctionalTest.php index 675e767a6..61df9e9be 100644 --- a/dev/FunctionalTest.php +++ b/dev/FunctionalTest.php @@ -39,6 +39,8 @@ class FunctionalTest extends SapphireTest { /** * CSSContentParser for the most recently requested page. + * + * @var CSSContentParser */ protected $cssParser = null; @@ -176,6 +178,8 @@ class FunctionalTest extends SapphireTest { /** * Return a CSSContentParser for the most recent content. + * + * @return CSSContentParser */ public function cssParser() { if(!$this->cssParser) $this->cssParser = new CSSContentParser($this->mainSession->lastContent()); diff --git a/dev/install/install.php5 b/dev/install/install.php5 index a3ad99b85..87eaddc84 100755 --- a/dev/install/install.php5 +++ b/dev/install/install.php5 @@ -48,7 +48,7 @@ $dirsToCheck = array( if($dirsToCheck[0] == $dirsToCheck[1]) { unset($dirsToCheck[1]); } -foreach($dirsToCheck as $dir) { +foreach($dirsToCheck as $dir) { //check this dir and every parent dir (until we hit the base of the drive) // or until we hit a dir we can't read do { diff --git a/docs/en/changelogs/3.0.10.md b/docs/en/changelogs/3.0.10.md new file mode 100644 index 000000000..8e06089aa --- /dev/null +++ b/docs/en/changelogs/3.0.10.md @@ -0,0 +1,26 @@ +# 3.0.10 + +## Upgrading + + * If relying on partial caching of content between logged in users, be aware that the cache is now automatically + segmented based on both the current member ID, and the versioned reading mode. If this is not an appropriate + method (such as if the same content is served to logged in users within partial caching) then it is necessary + to adjust the config value of `SSViewer.global_key` to something more or less sensitive. + +## Security + + * [BUG Fix issue with versioned dataobjects being cached between stages](https://github.com/silverstripe/silverstripe-framework/commit/4415a75d9304a3930b9c28763fc092299640c685) - See [announcement SS-2014-007](http://www.silverstripe.org/ss-2014-007-confidentiality-breach-can-occur-between-draft-and-live-modes/) + * [BUG Fix encoding of JS redirection script](https://github.com/silverstripe/silverstripe-framework/commit/f8e3bbe3ae3f29f22d85abb73cea033659511168) - See [announcement SS-2014-006](http://www.silverstripe.org/ss-2014-006-xss-in-returnurl-redirection/) + * [Amends solution to SS-2014-006](https://github.com/silverstripe/silverstripe-framework/commit/5b0a96979484fad12e11ce69aef98feda57b321f) + * [FIX Prevent SQLi when no URL filters are applied](https://github.com/silverstripe/silverstripe-cms/commit/114df8a3a5e4800ef7586c5d9c8d79798fd2a11d) - See [announcement SS-2014-004](http://www.silverstripe.org/ss-2014-004-sql-injection-in-sitetree-with-custom-urlsegmentfilter-rules/) + * [FIX Do now allow arbitary class creation in CMS](https://github.com/silverstripe/silverstripe-cms/commit/bf9b22fd4331a6f78cec12a75262f570b025ec2d) - See [announcement SS-2014-005](http://www.silverstripe.org/ss-2014-005-arbitrary-class-creation-in-cms-backend/) + +## General + + * [Rewrote usages of error suppression operator](https://github.com/silverstripe/silverstripe-framework/commit/6d5d3d8cb7e69e0b37471b1e34077211b0f631fe) + +## Changelog + + * [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.0.10) + * [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.0.10) + * [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.0.10) diff --git a/docs/en/changelogs/3.1.4.md b/docs/en/changelogs/3.1.4.md new file mode 100644 index 000000000..3d72a7f23 --- /dev/null +++ b/docs/en/changelogs/3.1.4.md @@ -0,0 +1,46 @@ +# 3.1.4 + +## Upgrading + + * If relying on partial caching of content between logged in users, be aware that the cache is now automatically + segmented based on both the current member ID, and the versioned reading mode. If this is not an appropriate + method (such as if the same content is served to logged in users within partial caching) then it is necessary + to adjust the config value of `SSViewer.global_key` to something more or less sensitive. + +## Security + + * [BUG Fix issue with versioned dataobjects being cached between stages](https://github.com/silverstripe/silverstripe-framework/commit/4415a75d9304a3930b9c28763fc092299640c685) - See [announcement SS-2014-007](http://www.silverstripe.org/ss-2014-007-confidentiality-breach-can-occur-between-draft-and-live-modes/) + * [BUG Fix encoding of JS redirection script](https://github.com/silverstripe/silverstripe-framework/commit/f8e3bbe3ae3f29f22d85abb73cea033659511168) - See [announcement SS-2014-006](http://www.silverstripe.org/ss-2014-006-xss-in-returnurl-redirection/) + * [Amends solution to SS-2014-006](https://github.com/silverstripe/silverstripe-framework/commit/5b0a96979484fad12e11ce69aef98feda57b321f) + * [FIX Prevent SQLi when no URL filters are applied](https://github.com/silverstripe/silverstripe-cms/commit/114df8a3a5e4800ef7586c5d9c8d79798fd2a11d) - See [announcement SS-2014-004](http://www.silverstripe.org/ss-2014-004-sql-injection-in-sitetree-with-custom-urlsegmentfilter-rules/) + * [FIX Do now allow arbitary class creation in CMS](https://github.com/silverstripe/silverstripe-cms/commit/bf9b22fd4331a6f78cec12a75262f570b025ec2d) - See [announcement SS-2014-005](http://www.silverstripe.org/ss-2014-005-arbitrary-class-creation-in-cms-backend/) + +## Bugfixes + + * [Fix Versioned::augmentSQL() when the data query was null.](https://github.com/silverstripe/silverstripe-framework/commit/deb1bfbcbaaa62acb2263ba797b5068e142a6353) + * [FIX UploadField validation error and styles](https://github.com/silverstripe/silverstripe-framework/commit/02bceca9b478358bdd569c16818d3be2467beb64) + * [FIX Overriding of theme templates in project folder](https://github.com/silverstripe/silverstripe-framework/commit/5f87d344f11c382dbee3fae8edfc00bb9a5a0265) + * [BUG Ensure TreeMultiSelectField doesn't populate menus with "unchanged".](https://github.com/silverstripe/silverstripe-framework/commit/9e2c7b657221c336137e07985bd5994682216d65) + * [BUG: #2503 Fixes performReadonlyTransformation for OptionSetField](https://github.com/silverstripe/silverstripe-framework/commit/44a8537f68872f0587cdf4cceadd433817dfdf60) + * [FIX: Rewrite Member getCMSFields to ensure updateCMSFields is only run once](https://github.com/silverstripe/silverstripe-framework/commit/d91c7d14b84d8b3caed948b0bbab94d254ea2b96) + * [FIX: Ensure valid CSS classes for GridField header](https://github.com/silverstripe/silverstripe-framework/commit/90952e7bd4bf7a278959ff320b3a71d30596f5d8) + * [BUG Fix case where setFolder('/') would break UploadField::fileexists](https://github.com/silverstripe/silverstripe-framework/commit/c1e0f98f87fa58edf7967d818732c7467cf47d80) + * [BUG Prevent unnecessary reconstruction of ClassName field after default records are generated](https://github.com/silverstripe/silverstripe-framework/commit/53b5adbcd98ff4d0e3947f4472b7b7b62a2b064a) + * [BUG Fix DataObject::loadLazyFields discarding original query parameters](https://github.com/silverstripe/silverstripe-framework/commit/23f5f08eda4201e0d3d4c28b81805da10b55bdb1) + * [Upload: retrieve existing File if an object without an ID is given and replaceFile=true](https://github.com/silverstripe/silverstripe-framework/commit/3c1e82b42c282ab64dfe7f5a68a50f59d8ebcc69) + * [BUG Fix Date and SS_DateTime::FormatFromSettings](https://github.com/silverstripe/silverstripe-framework/commit/84d8022b326e3938753430678cfc3dfa50770d83) + +## API + + * [Add support for many_many_extraField in YAML](https://github.com/silverstripe/silverstripe-framework/commit/8b923006227b0177983c96b949edaa6df18fbbf8) + * [Allow vetoing forgot password requests](https://github.com/silverstripe/silverstripe-framework/commit/9afcf8f01ac6b5c3c054b9a49f1731d35aa868ed) + +## General + + * [Rewrote usages of error suppression operator](https://github.com/silverstripe/silverstripe-framework/commit/6d5d3d8cb7e69e0b37471b1e34077211b0f631fe) + +## Changelog + + * [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.4) + * [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.4) + * [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.4) diff --git a/docs/en/changelogs/3.1.5.md b/docs/en/changelogs/3.1.5.md new file mode 100644 index 000000000..54647c1e8 --- /dev/null +++ b/docs/en/changelogs/3.1.5.md @@ -0,0 +1,9 @@ +# 3.1.5 + +## Upgrading + + * If running an application in an environment where user security is critical, it may be necessary to + assign the config value `Security.remember_username` to false. This will disable persistence of + user login name between sessions, and disable browser auto-completion on the username field. + Note that users of certain browsers who have previously autofilled and saved login credentials + will need to clear their password autofill history before this setting is properly respected. diff --git a/docs/en/installation/mac-osx.md b/docs/en/installation/mac-osx.md index 20f953245..e1460462f 100644 --- a/docs/en/installation/mac-osx.md +++ b/docs/en/installation/mac-osx.md @@ -25,13 +25,31 @@ Servers - this is so our changes to the php.ini take effect. ## Installing SilverStripe +###Composer +[Composer (a dependancy manager for PHP)](http://getcomposer.org) is the prefered way to install SilverStripe and ensure you get the correct set of files for your project. + +Composer uses your MAMP PHP executible to run and also requires [git](http://git-scm.com) (so it can automatically download the required files from GitHub). + +Process to install is as follows: + + 1. Install [git for mac](http://git-scm.com/download/mac). + 2. Install composer using the instructions at https://gist.github.com/kkirsche/5710272 + 3. Run the following command to get a fresh copy of SilverStripe via composer: + + composer create-project silverstripe/installer /Applications/MAMP/htdocs/silverstripe/ + + 4. You can now [use composer](http://doc.silverstripe.org/framework/en/installation/composer) to manage future SilverStripe updates and adding modules with a few easy commands. + +###Package Download + [Download](http://silverstripe.org/download) the latest SilverStripe installer package. Copy the tar.gz file to the 'Document Root' for MAMP - By Default its `/Applications/MAMP/htdocs`. Don't know what your Document Root is? Open MAMP Click `Preferences -> Apache`. Extract the tar.gz file to a folder, e.g. `silverstripe/` (you always move the tar.gz file first and not the other way around as SilverStripe uses a '.htaccess' file which is hidden from OSX so if you move SilverStripe the .htaccess file -won't come along. +won't come along. -Open your web browser and go to `http://localhost:8888/silverstripe/`. Enter your database details - by default with MAMP its user `root` and password `root` and select your account details. Click "Check Details". +###Run the installation wizard +Once you have a copy of the required code (by either of the above methods), open your web browser and go to `http://localhost:8888/silverstripe/`. Enter your database details - by default with MAMP its user `root` and password `root` and select your account details. Click "Check Details". Once everything is sorted hit "Install!" and Voila, you have SilverStripe installed diff --git a/docs/en/reference/dataobject.md b/docs/en/reference/dataobject.md index ec211ac7e..520f58828 100644 --- a/docs/en/reference/dataobject.md +++ b/docs/en/reference/dataobject.md @@ -247,7 +247,7 @@ Example: Check for CMS access permissions **Important**: These checks are not enforced on low-level ORM operations such as `write()` or `delete()`, but rather rely on being checked in the invoking code. The CMS default sections as well as custom interfaces like -`[ModelAdmin](/reference/modeladmin)` or `[GridField](/reference/gridfield)` +`[ModelAdmin](/reference/modeladmin)` or `[GridField](/reference/grid-field)` already enforce these permissions. ## Indexes diff --git a/docs/en/reference/datefield.md b/docs/en/reference/datefield.md index 663aee840..2933e56cf 100644 --- a/docs/en/reference/datefield.md +++ b/docs/en/reference/datefield.md @@ -62,7 +62,7 @@ HTML5 placeholders 'day', 'month' and 'year' are enabled by default. :::php DateField::create('MyDate') - ->setConfig('dmyfields', true); + ->setConfig('dmyfields', true) ->setConfig('dmyseparator', '/') // set the separator ->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders @@ -154,4 +154,4 @@ sensitive`! ### 4. Filename Use the original jQuery UI filename 'jquery.ui.datepicker-xx.js', where xx -stands for the locale. \ No newline at end of file +stands for the locale. diff --git a/docs/en/reference/execution-pipeline.md b/docs/en/reference/execution-pipeline.md index a2ee041f9..96c3a1b34 100644 --- a/docs/en/reference/execution-pipeline.md +++ b/docs/en/reference/execution-pipeline.md @@ -92,4 +92,4 @@ You can access the following controller-method with /team/signup ## SSViewer template rendering -See [templates](/topics/templates) for information on the SSViewer template system. +See [templates](/reference/templates) for information on the SSViewer template system. diff --git a/docs/en/reference/image.md b/docs/en/reference/image.md index 6583ec5cf..682183362 100644 --- a/docs/en/reference/image.md +++ b/docs/en/reference/image.md @@ -93,7 +93,7 @@ For output of an image tag with the image automatically resized to 80px width, y :::php $Image.SetWidth(80) // returns a image 80px wide, ratio kept the same - $Image.SetHeight(80) // returns a image 80px tall, ration kept the same + $Image.SetHeight(80) // returns a image 80px tall, ratio kept the same $Image.SetSize(80,80) // returns a 80x80px padded image $Image.SetRatioSize(80,80) // Returns an image scaled proportional, with its greatest diameter scaled to 80px $Image.CroppedImage(80,80) // Returns an 80x80 image cropped from the center. diff --git a/docs/en/topics/forms.md b/docs/en/topics/forms.md index 41eeff251..e05c9fedb 100644 --- a/docs/en/topics/forms.md +++ b/docs/en/topics/forms.md @@ -9,11 +9,11 @@ form. A fully implemented form in SilverStripe includes a couple of classes that individually have separate concerns. -* Controller—Takes care of assembling the form and receiving data from it. -* Form—Holds sets of fields, actions and validators. -* FormField —Fields that receive data or displays them, e.g input fields. -* FormActions—Often submit buttons that executes actions. -* Validators—Validate the whole form. +* Controller — Takes care of assembling the form and receiving data from it. +* Form — Holds sets of fields, actions and validators. +* FormField — Fields that receive data or displays them, e.g input fields. +* FormActions — Buttons that execute actions. +* Validators — Validate the whole form. Depending on your needs you can customize and override any of the above classes; the defaults, however, are often sufficient. diff --git a/docs/en/topics/security.md b/docs/en/topics/security.md index be5386d16..44493a9f0 100644 --- a/docs/en/topics/security.md +++ b/docs/en/topics/security.md @@ -445,6 +445,7 @@ In addition, you can tighten password security with the following configuration the user is blocked from further attempts for the timespan defined in `$lock_out_delay_mins` * `Member.lock_out_delay_mins`: Minutes of enforced lockout after incorrect password attempts. Only applies if `lock_out_after_incorrect_logins` is greater than 0. + * `Security.remember_username`: Set to false to disable autocomplete on login form ## Clickjacking: Prevent iframe Inclusion diff --git a/filesystem/Upload.php b/filesystem/Upload.php index ec8b7ca12..f02904baa 100644 --- a/filesystem/Upload.php +++ b/filesystem/Upload.php @@ -186,6 +186,7 @@ class Upload extends Controller { // This is to prevent it from trying to rename the file $this->file->Name = basename($relativeFilePath); $this->file->write(); + $this->file->onAfterUpload(); $this->extend('onAfterLoad', $this->file); //to allow extensions to e.g. create a version after an upload return true; } else { diff --git a/model/DataQuery.php b/model/DataQuery.php index 6f7723f14..07179f45c 100644 --- a/model/DataQuery.php +++ b/model/DataQuery.php @@ -422,7 +422,7 @@ class DataQuery { } /** - * Set the GROUP BY clause of this query. + * Append a GROUP BY clause to this query. * * @param String $groupby Escaped SQL statement */ @@ -432,7 +432,7 @@ class DataQuery { } /** - * Set the HAVING clause of this query. + * Append a HAVING clause to this query. * * @param String $having Escaped SQL statement */ @@ -464,7 +464,7 @@ class DataQuery { } /** - * Set the WHERE clause of this query. + * Append a WHERE clause to this query. * There are two different ways of doing this: * * @@ -485,7 +485,7 @@ class DataQuery { } /** - * Set a WHERE with OR. + * Append a WHERE with OR. * * @example $dataQuery->whereAny(array("\"Monkey\" = 'Chimp'", "\"Color\" = 'Brown'")); * @see where() diff --git a/model/Image.php b/model/Image.php index 19a614ad0..40c0c351a 100644 --- a/model/Image.php +++ b/model/Image.php @@ -718,6 +718,11 @@ class Image extends File { return self::ORIENTATION_SQUARE; } } + + public function onAfterUpload() { + $this->deleteFormattedImages(); + parent::onAfterUpload(); + } protected function onBeforeDelete() { parent::onBeforeDelete(); diff --git a/security/ChangePasswordForm.php b/security/ChangePasswordForm.php index be5d90313..42106748f 100644 --- a/security/ChangePasswordForm.php +++ b/security/ChangePasswordForm.php @@ -57,6 +57,7 @@ class ChangePasswordForm extends Form { * Change the password * * @param array $data The user submitted data + * @return SS_HTTPResponse */ public function doChangePassword(array $data) { if($member = Member::currentUser()) { @@ -68,8 +69,7 @@ class ChangePasswordForm extends Form { "bad" ); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. - $this->controller->redirect($this->controller->Link('changepassword')); - return; + return $this->controller->redirect($this->controller->Link('changepassword')); } } @@ -81,8 +81,7 @@ class ChangePasswordForm extends Form { // The user is not logged in and no valid auto login hash is available if(!$member) { Session::clear('AutoLoginHash'); - $this->controller->redirect($this->controller->Link('login')); - return; + return $this->controller->redirect($this->controller->Link('login')); } } @@ -94,8 +93,7 @@ class ChangePasswordForm extends Form { "bad"); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. - $this->controller->redirect($this->controller->Link('changepassword')); - return; + return $this->controller->redirect($this->controller->Link('changepassword')); } else if($data['NewPassword1'] == $data['NewPassword2']) { $isValid = $member->changePassword($data['NewPassword1']); @@ -115,7 +113,7 @@ class ChangePasswordForm extends Form { // absolute redirection URLs may cause spoofing && Director::is_site_url($_REQUEST['BackURL']) ) { - $this->controller->redirect($_REQUEST['BackURL']); + return $this->controller->redirect($_REQUEST['BackURL']); } else { // Redirect to default location - the login form saying "You are logged in as..." @@ -123,7 +121,7 @@ class ChangePasswordForm extends Form { 'BackURL', Director::absoluteBaseURL(), $this->controller->Link('login') ); - $this->controller->redirect($redirectURL); + return $this->controller->redirect($redirectURL); } } else { $this->clearMessage(); @@ -137,7 +135,7 @@ class ChangePasswordForm extends Form { ); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. - $this->controller->redirect($this->controller->Link('changepassword')); + return $this->controller->redirect($this->controller->Link('changepassword')); } } else { @@ -147,7 +145,7 @@ class ChangePasswordForm extends Form { "bad"); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. - $this->controller->redirect($this->controller->Link('changepassword')); + return $this->controller->redirect($this->controller->Link('changepassword')); } } diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index 6744a639a..ac83e7942 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -80,9 +80,16 @@ class MemberLoginForm extends LoginForm { new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this), // Regardless of what the unique identifer field is (usually 'Email'), it will be held in the // 'Email' value, below: - new TextField("Email", $label, Session::get('SessionForms.MemberLoginForm.Email'), null, $this), + $emailField = new TextField("Email", $label, null, null, $this), new PasswordField("Password", _t('Member.PASSWORD', 'Password')) ); + if(Security::config()->remember_username) { + $emailField->setValue(Session::get('SessionForms.MemberLoginForm.Email')); + } else { + // Some browsers won't respect this attribute unless it's added to the form + $this->setAttribute('autocomplete', 'off'); + $emailField->setAttribute('autocomplete', 'off'); + } if(Security::config()->autologin_enabled) { $fields->push(new CheckboxField( "Remember", @@ -114,13 +121,13 @@ class MemberLoginForm extends LoginForm { $this->setValidator(new RequiredFields('Email', 'Password')); // Focus on the email input when the page is loaded - Requirements::customScript(<<assertEquals($rootURL, Director::absoluteURL($rootURL)); + $this->assertEquals($rootURL, Director::absoluteURL($rootURL, true)); + $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com')); + $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', true)); + $this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test")); + $this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", true)); + + // Test relative to base + $this->assertEquals("$rootURL/mysite/test", Director::absoluteURL("test", true)); + $this->assertEquals("$rootURL/mysite/test/url", Director::absoluteURL("test/url", true)); + $this->assertEquals("$rootURL/root", Director::absoluteURL("/root", true)); + $this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", true)); + + // Test relative to requested page + $this->assertEquals("$rootURL/mysite/sub-page/test", Director::absoluteURL("test")); + // Legacy behaviour resolves this to $rootURL/mysite/test/url + //$this->assertEquals("$rootURL/mysite/sub-page/test/url", Director::absoluteURL("test/url")); + $this->assertEquals("$rootURL/root", Director::absoluteURL("/root")); + $this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url")); + + // Test that javascript links are not left intact + $this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")')); + $this->assertStringStartsNotWith('alert', Director::absoluteURL('javascript:alert("attack")')); + $this->assertStringStartsNotWith('javascript', Director::absoluteURL('alert("attack")')); + $this->assertStringStartsNotWith('alert', Director::absoluteURL('alert("attack")')); + } public function testAlternativeBaseURL() { // relative base URLs - you should end them in a / diff --git a/tests/filesystem/UploadTest.php b/tests/filesystem/UploadTest.php index 48a833027..2649e36a3 100644 --- a/tests/filesystem/UploadTest.php +++ b/tests/filesystem/UploadTest.php @@ -465,6 +465,48 @@ class UploadTest extends SapphireTest { $file3->delete(); } + public function testDeleteResampledImagesOnUpload() { + $tmpFileName = 'UploadTest-testUpload.jpg'; + $tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; + + $uploadImage = function() use ($tmpFileName, $tmpFilePath) { + copy(__DIR__ . '/gdtest/test_jpg.jpg', $tmpFilePath); + + // emulates the $_FILES array + $tmpFile = array( + 'name' => $tmpFileName, + 'type' => 'text/plaintext', + 'size' => filesize($tmpFilePath), + 'tmp_name' => $tmpFilePath, + 'extension' => 'jpg', + 'error' => UPLOAD_ERR_OK, + ); + + $v = new UploadTest_Validator(); + + // test upload into default folder + $u = new Upload(); + $u->setReplaceFile(true); + $u->setValidator($v); + $u->load($tmpFile); + return $u->getFile(); + }; + + // Image upload and generate a resampled image + $image = $uploadImage(); + $resampled = $image->ResizedImage(123, 456); + $resampledPath = $resampled->getFullPath(); + $this->assertTrue(file_exists($resampledPath)); + + // Re-upload the image, overwriting the original + // Resampled images should removed when their parent file is overwritten + $image = $uploadImage(); + $this->assertFalse(file_exists($resampledPath)); + + unlink($tmpFilePath); + $image->delete(); + } + } class UploadTest_Validator extends Upload_Validator implements TestOnly { diff --git a/tests/security/SecurityTest.php b/tests/security/SecurityTest.php index c566fbf12..d1f1ffde2 100644 --- a/tests/security/SecurityTest.php +++ b/tests/security/SecurityTest.php @@ -15,6 +15,8 @@ class SecurityTest extends FunctionalTest { protected $priorDefaultAuthenticator = null; protected $priorUniqueIdentifierField = null; + + protected $priorRememberUsername = null; public function setUp() { // This test assumes that MemberAuthenticator is present and the default @@ -29,6 +31,7 @@ class SecurityTest extends FunctionalTest { // And that the unique identified field is 'Email' $this->priorUniqueIdentifierField = Member::config()->unique_identifier_field; + $this->priorRememberUsername = Security::config()->remember_username; Member::config()->unique_identifier_field = 'Email'; parent::setUp(); @@ -48,6 +51,7 @@ class SecurityTest extends FunctionalTest { // Restore unique identifier field Member::config()->unique_identifier_field = $this->priorUniqueIdentifierField; + Security::config()->remember_username = $this->priorRememberUsername; parent::tearDown(); } @@ -124,6 +128,32 @@ class SecurityTest extends FunctionalTest { $this->session()->inst_set('loggedInAs', null); } + public function testLoginUsernamePersists() { + // Test that username does not persist + $this->session()->inst_set('SessionForms.MemberLoginForm.Email', 'myuser@silverstripe.com'); + Security::config()->remember_username = false; + $this->get(Config::inst()->get('Security', 'login_url')); + $items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm #Email input.text'); + $this->assertEquals(1, count($items)); + $this->assertEmpty((string)$items[0]->attributes()->value); + $this->assertEquals('off', (string)$items[0]->attributes()->autocomplete); + $form = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm'); + $this->assertEquals(1, count($form)); + $this->assertEquals('off', (string)$form[0]->attributes()->autocomplete); + + // Test that username does persist when necessary + $this->session()->inst_set('SessionForms.MemberLoginForm.Email', 'myuser@silverstripe.com'); + Security::config()->remember_username = true; + $this->get(Config::inst()->get('Security', 'login_url')); + $items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm #Email input.text'); + $this->assertEquals(1, count($items)); + $this->assertEquals('myuser@silverstripe.com', (string)$items[0]->attributes()->value); + $this->assertNotEquals('off', (string)$items[0]->attributes()->autocomplete); + $form = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm'); + $this->assertEquals(1, count($form)); + $this->assertNotEquals('off', (string)$form[0]->attributes()->autocomplete); + } + public function testExternalBackUrlRedirectionDisallowed() { // Test internal relative redirect $response = $this->doTestLoginForm('noexpiry@silverstripe.com', '1nitialPassword', 'testpage'); diff --git a/thirdparty/tinymce/tiny_mce_gzip.php b/thirdparty/tinymce/tiny_mce_gzip.php index 8c571689c..d07fe1b5f 100755 --- a/thirdparty/tinymce/tiny_mce_gzip.php +++ b/thirdparty/tinymce/tiny_mce_gzip.php @@ -94,17 +94,17 @@ class TinyMCE_Compressor { $plugins = self::getParam("plugins"); if ($plugins) $this->settings["plugins"] = $plugins; - $plugins = explode(',', $this->settings["plugins"]); + $plugins = array_unique(explode(',', $this->settings["plugins"])); $themes = self::getParam("themes"); if ($themes) $this->settings["themes"] = $themes; - $themes = explode(',', $this->settings["themes"]); + $themes = array_unique(explode(',', $this->settings["themes"])); $languages = self::getParam("languages"); if ($languages) $this->settings["languages"] = $languages; - $languages = explode(',', $this->settings["languages"]); + $languages = array_unique(explode(',', $this->settings["languages"])); $tagFiles = self::getParam("files"); if ($tagFiles) @@ -140,7 +140,7 @@ class TinyMCE_Compressor { } // Add any specified files. - $allFiles = array_merge($files, explode(',', $this->settings['files'])); + $allFiles = array_merge($files, array_unique(explode(',', $this->settings['files']))); // Process source files for ($i = 0; $i < count($allFiles); $i++) {