Merge remote-tracking branch 'origin/3.1'

This commit is contained in:
Damian Mooyman 2014-04-22 12:09:51 +12:00
commit 982ad569b9
24 changed files with 277 additions and 38 deletions

View File

@ -413,7 +413,12 @@ class Director implements TemplateGlobalProvider {
/** /**
* Turns the given URL into an absolute URL. * 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) { public static function absoluteURL($url, $relativeToSiteBase = false) {
if(!isset($_SERVER['REQUEST_URI'])) return false; if(!isset($_SERVER['REQUEST_URI'])) return false;

View File

@ -44,6 +44,7 @@ class SS_HTTPResponse {
416 => 'Request Range Not Satisfiable', 416 => 'Request Range Not Satisfiable',
417 => 'Expectation Failed', 417 => 'Expectation Failed',
422 => 'Unprocessable Entity', 422 => 'Unprocessable Entity',
429 => 'Too Many Requests',
500 => 'Internal Server Error', 500 => 'Internal Server Error',
501 => 'Not Implemented', 501 => 'Not Implemented',
502 => 'Bad Gateway', 502 => 'Bad Gateway',
@ -234,7 +235,7 @@ class SS_HTTPResponse {
} }
if(in_array($this->statusCode, self::$redirect_codes) && headers_sent($file, $line)) { 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); $urlATT = Convert::raw2htmlatt($url);
$urlJS = Convert::raw2js($url); $urlJS = Convert::raw2js($url);
echo echo

View File

@ -39,6 +39,8 @@ class FunctionalTest extends SapphireTest {
/** /**
* CSSContentParser for the most recently requested page. * CSSContentParser for the most recently requested page.
*
* @var CSSContentParser
*/ */
protected $cssParser = null; protected $cssParser = null;
@ -176,6 +178,8 @@ class FunctionalTest extends SapphireTest {
/** /**
* Return a CSSContentParser for the most recent content. * Return a CSSContentParser for the most recent content.
*
* @return CSSContentParser
*/ */
public function cssParser() { public function cssParser() {
if(!$this->cssParser) $this->cssParser = new CSSContentParser($this->mainSession->lastContent()); if(!$this->cssParser) $this->cssParser = new CSSContentParser($this->mainSession->lastContent());

View File

@ -48,7 +48,7 @@ $dirsToCheck = array(
if($dirsToCheck[0] == $dirsToCheck[1]) { if($dirsToCheck[0] == $dirsToCheck[1]) {
unset($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) //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 // or until we hit a dir we can't read
do { do {

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -25,6 +25,23 @@ Servers - this is so our changes to the php.ini take effect.
## Installing SilverStripe ## 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`. [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`. Don't know what your Document Root is? Open MAMP Click `Preferences -> Apache`.
@ -32,6 +49,7 @@ Extract the tar.gz file to a folder, e.g. `silverstripe/` (you always move the t
around as SilverStripe uses a '.htaccess' file which is hidden from OSX so if you move SilverStripe the .htaccess file 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 Once everything is sorted hit "Install!" and Voila, you have SilverStripe installed

View File

@ -247,7 +247,7 @@ Example: Check for CMS access permissions
**Important**: These checks are not enforced on low-level ORM operations **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. 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 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. already enforce these permissions.
## Indexes ## Indexes

View File

@ -62,7 +62,7 @@ HTML5 placeholders 'day', 'month' and 'year' are enabled by default.
:::php :::php
DateField::create('MyDate') DateField::create('MyDate')
->setConfig('dmyfields', true); ->setConfig('dmyfields', true)
->setConfig('dmyseparator', '/') // set the separator ->setConfig('dmyseparator', '/') // set the separator
->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders ->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders

View File

@ -92,4 +92,4 @@ You can access the following controller-method with /team/signup
## SSViewer template rendering ## 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.

View File

@ -93,7 +93,7 @@ For output of an image tag with the image automatically resized to 80px width, y
:::php :::php
$Image.SetWidth(80) // returns a image 80px wide, ratio kept the same $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.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.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. $Image.CroppedImage(80,80) // Returns an 80x80 image cropped from the center.

View File

@ -9,11 +9,11 @@ form.
A fully implemented form in SilverStripe includes a couple of classes that A fully implemented form in SilverStripe includes a couple of classes that
individually have separate concerns. individually have separate concerns.
* Controller—Takes care of assembling the form and receiving data from it. * Controller Takes care of assembling the form and receiving data from it.
* Form—Holds sets of fields, actions and validators. * Form Holds sets of fields, actions and validators.
* FormField —Fields that receive data or displays them, e.g input fields. * FormField — Fields that receive data or displays them, e.g input fields.
* FormActions—Often submit buttons that executes actions. * FormActions — Buttons that execute actions.
* Validators—Validate the whole form. * Validators Validate the whole form.
Depending on your needs you can customize and override any of the above classes; Depending on your needs you can customize and override any of the above classes;
the defaults, however, are often sufficient. the defaults, however, are often sufficient.

View File

@ -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` 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. * `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. 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 ## Clickjacking: Prevent iframe Inclusion

View File

@ -186,6 +186,7 @@ class Upload extends Controller {
// This is to prevent it from trying to rename the file // This is to prevent it from trying to rename the file
$this->file->Name = basename($relativeFilePath); $this->file->Name = basename($relativeFilePath);
$this->file->write(); $this->file->write();
$this->file->onAfterUpload();
$this->extend('onAfterLoad', $this->file); //to allow extensions to e.g. create a version after an upload $this->extend('onAfterLoad', $this->file); //to allow extensions to e.g. create a version after an upload
return true; return true;
} else { } else {

View File

@ -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 * @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 * @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: * There are two different ways of doing this:
* *
* <code> * <code>
@ -485,7 +485,7 @@ class DataQuery {
} }
/** /**
* Set a WHERE with OR. * Append a WHERE with OR.
* *
* @example $dataQuery->whereAny(array("\"Monkey\" = 'Chimp'", "\"Color\" = 'Brown'")); * @example $dataQuery->whereAny(array("\"Monkey\" = 'Chimp'", "\"Color\" = 'Brown'"));
* @see where() * @see where()

View File

@ -719,6 +719,11 @@ class Image extends File {
} }
} }
public function onAfterUpload() {
$this->deleteFormattedImages();
parent::onAfterUpload();
}
protected function onBeforeDelete() { protected function onBeforeDelete() {
parent::onBeforeDelete(); parent::onBeforeDelete();

View File

@ -57,6 +57,7 @@ class ChangePasswordForm extends Form {
* Change the password * Change the password
* *
* @param array $data The user submitted data * @param array $data The user submitted data
* @return SS_HTTPResponse
*/ */
public function doChangePassword(array $data) { public function doChangePassword(array $data) {
if($member = Member::currentUser()) { if($member = Member::currentUser()) {
@ -68,8 +69,7 @@ class ChangePasswordForm extends Form {
"bad" "bad"
); );
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere. // 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'));
return;
} }
} }
@ -81,8 +81,7 @@ class ChangePasswordForm extends Form {
// The user is not logged in and no valid auto login hash is available // The user is not logged in and no valid auto login hash is available
if(!$member) { if(!$member) {
Session::clear('AutoLoginHash'); Session::clear('AutoLoginHash');
$this->controller->redirect($this->controller->Link('login')); return $this->controller->redirect($this->controller->Link('login'));
return;
} }
} }
@ -94,8 +93,7 @@ class ChangePasswordForm extends Form {
"bad"); "bad");
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere. // 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'));
return;
} }
else if($data['NewPassword1'] == $data['NewPassword2']) { else if($data['NewPassword1'] == $data['NewPassword2']) {
$isValid = $member->changePassword($data['NewPassword1']); $isValid = $member->changePassword($data['NewPassword1']);
@ -115,7 +113,7 @@ class ChangePasswordForm extends Form {
// absolute redirection URLs may cause spoofing // absolute redirection URLs may cause spoofing
&& Director::is_site_url($_REQUEST['BackURL']) && Director::is_site_url($_REQUEST['BackURL'])
) { ) {
$this->controller->redirect($_REQUEST['BackURL']); return $this->controller->redirect($_REQUEST['BackURL']);
} }
else { else {
// Redirect to default location - the login form saying "You are logged in as..." // Redirect to default location - the login form saying "You are logged in as..."
@ -123,7 +121,7 @@ class ChangePasswordForm extends Form {
'BackURL', 'BackURL',
Director::absoluteBaseURL(), $this->controller->Link('login') Director::absoluteBaseURL(), $this->controller->Link('login')
); );
$this->controller->redirect($redirectURL); return $this->controller->redirect($redirectURL);
} }
} else { } else {
$this->clearMessage(); $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. // 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 { } else {
@ -147,7 +145,7 @@ class ChangePasswordForm extends Form {
"bad"); "bad");
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere. // 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'));
} }
} }

View File

@ -80,9 +80,16 @@ class MemberLoginForm extends LoginForm {
new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this), new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this),
// Regardless of what the unique identifer field is (usually 'Email'), it will be held in the // Regardless of what the unique identifer field is (usually 'Email'), it will be held in the
// 'Email' value, below: // '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')) 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) { if(Security::config()->autologin_enabled) {
$fields->push(new CheckboxField( $fields->push(new CheckboxField(
"Remember", "Remember",
@ -114,13 +121,13 @@ class MemberLoginForm extends LoginForm {
$this->setValidator(new RequiredFields('Email', 'Password')); $this->setValidator(new RequiredFields('Email', 'Password'));
// Focus on the email input when the page is loaded // Focus on the email input when the page is loaded
Requirements::customScript(<<<JS $js = <<<JS
(function() { (function() {
var el = document.getElementById("MemberLoginForm_LoginForm_Email"); var el = document.getElementById("MemberLoginForm_LoginForm_Email");
if(el && el.focus) el.focus(); if(el && el.focus && (!jQuery || jQuery(el).is(':visible'))) el.focus();
})(); })();
JS JS;
); Requirements::customScript($js, 'MemberLoginFormFieldFocus');
} }
/** /**

View File

@ -63,6 +63,15 @@ class Security extends Controller implements TemplateGlobalProvider {
*/ */
private static $autologin_enabled = true; private static $autologin_enabled = true;
/**
* Determine if login username may be remembered between login sessions
* If set to false this will disable autocomplete and prevent username persisting in the session
*
* @config
* @var bool
*/
private static $remember_username = true;
/** /**
* Location of word list to use for generating passwords * Location of word list to use for generating passwords
* *

View File

@ -41,6 +41,9 @@ class DirectorTest extends SapphireTest {
public function tearDown() { public function tearDown() {
// TODO Remove director rule, currently API doesnt allow this // TODO Remove director rule, currently API doesnt allow this
// Remove base URL override (setting to false reverts to default behaviour)
Director::setBaseURL(false);
// Reinstate the original REQUEST_URI after it was modified by some tests // Reinstate the original REQUEST_URI after it was modified by some tests
$_SERVER['REQUEST_URI'] = self::$originalRequestURI; $_SERVER['REQUEST_URI'] = self::$originalRequestURI;
@ -73,6 +76,40 @@ class DirectorTest extends SapphireTest {
unlink($tempFilePath); unlink($tempFilePath);
} }
public function testAbsoluteURL() {
$rootURL = Director::protocolAndHost();
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
Director::setBaseURL('/mysite/');
// Test already absolute url
$this->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() { public function testAlternativeBaseURL() {
// relative base URLs - you should end them in a / // relative base URLs - you should end them in a /
Config::inst()->update('Director', 'alternate_base_url', '/relativebase/'); Config::inst()->update('Director', 'alternate_base_url', '/relativebase/');

View File

@ -465,6 +465,48 @@ class UploadTest extends SapphireTest {
$file3->delete(); $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 { class UploadTest_Validator extends Upload_Validator implements TestOnly {

View File

@ -16,6 +16,8 @@ class SecurityTest extends FunctionalTest {
protected $priorUniqueIdentifierField = null; protected $priorUniqueIdentifierField = null;
protected $priorRememberUsername = null;
public function setUp() { public function setUp() {
// This test assumes that MemberAuthenticator is present and the default // This test assumes that MemberAuthenticator is present and the default
$this->priorAuthenticators = Authenticator::get_authenticators(); $this->priorAuthenticators = Authenticator::get_authenticators();
@ -29,6 +31,7 @@ class SecurityTest extends FunctionalTest {
// And that the unique identified field is 'Email' // And that the unique identified field is 'Email'
$this->priorUniqueIdentifierField = Member::config()->unique_identifier_field; $this->priorUniqueIdentifierField = Member::config()->unique_identifier_field;
$this->priorRememberUsername = Security::config()->remember_username;
Member::config()->unique_identifier_field = 'Email'; Member::config()->unique_identifier_field = 'Email';
parent::setUp(); parent::setUp();
@ -48,6 +51,7 @@ class SecurityTest extends FunctionalTest {
// Restore unique identifier field // Restore unique identifier field
Member::config()->unique_identifier_field = $this->priorUniqueIdentifierField; Member::config()->unique_identifier_field = $this->priorUniqueIdentifierField;
Security::config()->remember_username = $this->priorRememberUsername;
parent::tearDown(); parent::tearDown();
} }
@ -124,6 +128,32 @@ class SecurityTest extends FunctionalTest {
$this->session()->inst_set('loggedInAs', null); $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() { public function testExternalBackUrlRedirectionDisallowed() {
// Test internal relative redirect // Test internal relative redirect
$response = $this->doTestLoginForm('noexpiry@silverstripe.com', '1nitialPassword', 'testpage'); $response = $this->doTestLoginForm('noexpiry@silverstripe.com', '1nitialPassword', 'testpage');

View File

@ -94,17 +94,17 @@ class TinyMCE_Compressor {
$plugins = self::getParam("plugins"); $plugins = self::getParam("plugins");
if ($plugins) if ($plugins)
$this->settings["plugins"] = $plugins; $this->settings["plugins"] = $plugins;
$plugins = explode(',', $this->settings["plugins"]); $plugins = array_unique(explode(',', $this->settings["plugins"]));
$themes = self::getParam("themes"); $themes = self::getParam("themes");
if ($themes) if ($themes)
$this->settings["themes"] = $themes; $this->settings["themes"] = $themes;
$themes = explode(',', $this->settings["themes"]); $themes = array_unique(explode(',', $this->settings["themes"]));
$languages = self::getParam("languages"); $languages = self::getParam("languages");
if ($languages) if ($languages)
$this->settings["languages"] = $languages; $this->settings["languages"] = $languages;
$languages = explode(',', $this->settings["languages"]); $languages = array_unique(explode(',', $this->settings["languages"]));
$tagFiles = self::getParam("files"); $tagFiles = self::getParam("files");
if ($tagFiles) if ($tagFiles)
@ -140,7 +140,7 @@ class TinyMCE_Compressor {
} }
// Add any specified files. // 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 // Process source files
for ($i = 0; $i < count($allFiles); $i++) { for ($i = 0; $i < count($allFiles); $i++) {