Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Improved docs on $allowed_actions
Added section to "Controllers" and "Form" topics,
added $allowed_actions definitions to all controller examples
  • Loading branch information
chillu committed Feb 17, 2013
1 parent f06ba70 commit d51e0bc
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 8 deletions.
3 changes: 3 additions & 0 deletions docs/en/howto/csv-import.md
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions docs/en/howto/simple-contact-form.md
Expand Up @@ -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'),
Expand Down Expand Up @@ -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() {
// ...
}
Expand Down
1 change: 1 addition & 0 deletions docs/en/reference/execution-pipeline.md
Expand Up @@ -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');
}
Expand Down
2 changes: 2 additions & 0 deletions docs/en/reference/grid-field.md
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions docs/en/reference/templates.md
Expand Up @@ -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();
Expand Down
99 changes: 96 additions & 3 deletions docs/en/topics/controller.md
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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'
);
Expand Down
1 change: 1 addition & 0 deletions docs/en/topics/form-validation.md
Expand Up @@ -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(
Expand Down
19 changes: 15 additions & 4 deletions docs/en/topics/forms.md
Expand Up @@ -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() {
Expand All @@ -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**

Expand Down
4 changes: 3 additions & 1 deletion docs/en/topics/security.md
Expand Up @@ -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]}'");
Expand All @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions docs/en/tutorials/3-forms.md
Expand Up @@ -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() {
Expand Down

0 comments on commit d51e0bc

Please sign in to comment.