Permalink
Browse files

Merge remote-tracking branch 'origin/3.0' into 3.1

  • Loading branch information...
2 parents 5a4d5e1 + 37b8034 commit 7efae6b95f798377a855e4bc8a66b5ad22c0f7fd Hamish Friedlander committed Feb 18, 2013
View
Oops, something went wrong.
@@ -1051,6 +1051,7 @@ jQuery.noConflict();
}(jQuery));
var statusMessage = function(text, type) {
+ text = $('<div/>').text(text).html(); // Escape HTML entities in text
jQuery.noticeAdd({text: text, type: type});
};
View
@@ -265,7 +265,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 */
View
@@ -88,7 +88,7 @@ class RequestHandler extends ViewableData {
* </code>
*
* 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.
@@ -322,21 +322,30 @@ public function checkAccessAction($action) {
// 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
View
@@ -1289,6 +1289,8 @@ ErrorDocument 500 /assets/error-500.html
<IfModule mod_alias.c>
RedirectMatch 403 /silverstripe-cache(/|$)
+ RedirectMatch 403 /vendor(/|$)
+ RedirectMatch 403 /composer\.(json|lock)
</IfModule>
<IfModule mod_rewrite.c>
View
Oops, something went wrong.
@@ -11,6 +11,8 @@ For information on how to upgrade to newer versions consult the [upgrading](/ins
* [3.1.0](3.1.0) - Unreleased
+ * [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
@@ -68,6 +68,9 @@ You can have more customized logic and interface feedback through a custom contr
:::php
<?php
class MyController extends Controller {
+
+ static $allowed_actions = array('Form');
+
protected $template = "BlankPage";
public function Link($action = null) {
@@ -9,6 +9,7 @@ Let's start by defining a new `ContactPage` page type:
class ContactPage extends Page {
}
class ContactPage_Controller extends Page_Controller {
+ static $allowed_actions = array('Form');
public function Form() {
$fields = new FieldList(
new TextField('Name'),
@@ -60,6 +61,7 @@ Now that we have a contact form, we need some way of collecting the data submitt
:::php
class ContactPage_Controller extends Page_Controller {
+ static $allowed_actions = array('Form');
public function Form() {
// ...
}
@@ -79,6 +79,7 @@ You can access the following controller-method with /team/signup
class Team extends DataObject {}
class Team_Controller extends Controller {
+ static $allowed_actions = array('signup');
public function signup($id, $otherId) {
return $this->renderWith('MyTemplate');
}
@@ -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();
@@ -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();
@@ -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.
<?php
class FastFood_Controller extends Controller {
- public function order($arguments) {
+ public static $allowed_actions = array('order');
+ public function order(SS_HTTPRequest $request) {
print_r($arguments);
}
}
+## Routing
+
`mysite/_config/routes.yml`
:::yaml
@@ -44,6 +47,96 @@ making any code changes to your controller.
[Name] => cheesefries
)
+<div class="warning" markdown='1'>
+ 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`.
+</div>
+
+## 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(<status-code>)` 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'
);
@@ -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(
View
@@ -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**
@@ -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 = '<p>Your results for:' . Convert::raw2xml($request->getVar('Query')) . '</p>';
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);
@@ -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() {
@@ -37,21 +37,27 @@ public function Field($properties = array()) {
$value = ($this->value && !array_key_exists($this->value, $this->source)) ? $this->value : null;
$checked = ($value) ? " checked=\"checked\"" : '';
$options .= "<li class=\"valCustom\">"
- . sprintf("<input id=\"%s_custom\" name=\"%s\" type=\"radio\" value=\"__custom__\" class=\"radio\" %s />",
- $itemID, $this->name, $checked)
- . sprintf('<label for="%s_custom">%s:</label>',
- $itemID, _t('MemberDatetimeOptionsetField.Custom', 'Custom'))
. sprintf(
- "<input class=\"customFormat cms-help cms-help-tooltip\" name=\"%s_custom\" value=\"%s\" />\n",
- $this->name,
- $value
+ "<input id=\"%s_custom\" name=\"%s\" type=\"radio\" value=\"__custom__\" class=\"radio\" %s />",
+ $itemID, $this->name,
+ $checked
+ )
+ . sprintf(
+ '<label for="%s_custom">%s:</label>',
+ $itemID, _t('MemberDatetimeOptionsetField.Custom', 'Custom')
)
- . sprintf("<input type=\"hidden\" class=\"formatValidationURL\" value=\"%s\" />",
- $this->Link() . '/validate');
+ . sprintf(
+ "<input class=\"customFormat cms-help cms-help-tooltip\" name=\"%s_custom\" value=\"%s\" />\n",
+ $this->name, Convert::raw2xml($value)
+ )
+ . sprintf(
+ "<input type=\"hidden\" class=\"formatValidationURL\" value=\"%s\" />",
+ $this->Link() . '/validate'
+ );
$options .= ($value) ? sprintf(
'<span class="preview">(%s: "%s")</span>',
_t('MemberDatetimeOptionsetField.Preview', 'Preview'),
- Zend_Date::now()->toString($value)
+ Convert::raw2xml(Zend_Date::now()->toString($value))
) : '';
$id = $this->id();
Oops, something went wrong.

0 comments on commit 7efae6b

Please sign in to comment.