From d51e0bc2ec7046cb319964bb84f2c882c553fa0d Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 28 Jan 2013 22:35:32 +0100 Subject: [PATCH] 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 5c8253397d3..b53ce3c0264 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 465786a1efe..6d4aeb2ffd8 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 1fe556ddeba..81b3ca0647a 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 268e4aaf6df..cfd80041069 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 ccd7906f9e3..a99a3b4219f 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 5844912e807..5ac3189f3bf 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 fff3e792d38..efd48b67ff0 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 8f8435660de..635014f1cfb 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() {