Improved docs on $allowed_actions

Added section to "Controllers" and "Form" topics,
added $allowed_actions definitions to all controller examples
This commit is contained in:
Ingo Schommer 2013-01-28 22:35:32 +01:00
parent 50995fbecb
commit 3e27d27f7a
8 changed files with 153 additions and 53 deletions

View File

@ -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";
function Link($action = null) {

View File

@ -83,11 +83,12 @@ You can access the following controller-method with /team/signup
class Team extends DataObject {}
class Team_Controller extends Controller {
function signup($id, $otherId) {
static $allowed_actions = array('signup');
public function signup($id, $otherId) {
return $this->renderWith('MyTemplate');
}
}
## SSViewer template rendering
See [templates](/topics/templates) for information on the SSViewer template system.
See [templates](/topics/templates) for information on the SSViewer template system.

View File

@ -98,6 +98,7 @@ This code provides a good template:
:::php
class MyProcess extends Controller {
public static $allowed_actions = array('index');
function index() {
set_time_limit(0);
while(memory_get_usage() < 32*1024*1024) {

View File

@ -3,8 +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
`mysite/code/Controllers/FastFood.php`
@ -12,11 +11,12 @@ your SilverStripe site.
<?php
class FastFood_Controller extends Controller {
function order($arguments) {
public static $allowed_actions = array('order');
public function order(SS_HTTPRequest $request) {
print_r($arguments);
}
}
?>
@ -37,6 +37,96 @@ Request for `/fastfood/order/24/cheesefries` would result in the following to th
[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
@ -51,9 +141,10 @@ the case below we also want any orders coming through `/fastfood/drivethrough/`
:::php
class FastFood_Controller extends Controller {
static $allowed_actions = array('drivethrough');
public static $url_handlers = array(
'drivethrough/$Action/$ID/$Name' => 'order'
);
'drivethrough/$Action/$ID/$Name' => 'order'
);

View File

@ -45,16 +45,16 @@ TODO Describe behaviour.js solution easily, how to disable it
Setting fieldEl.requiredErrorMsg or formEl.requiredErrorMsg will override the default error message. Both can include
the string '$FieldLabel', which will be replaced with the field's label. Otherwise, the message is "Please fill out
"$FieldLabel", it is required".
You can use Behaviour to load in the appropriate value:
:::js
Behaviour.register({
'#Form_Form' : {
requiredErrorMsg: "Please complete this question before moving on.",
}
}
});
### Other validation libraries
By default, SilverStripe forms with an attached Validator instance use the custom Validator.js clientside logic. It is
@ -65,12 +65,12 @@ Disable for all forms (in `mysite/_config.php`):
:::php
Validator::set_javascript_validation_handler('none');
Disable for a specific form:
:::php
$myForm->getValidator()->setJavascriptValidationHandler('none');
## Related

View File

@ -7,7 +7,7 @@ instantiating the Form class itself, or by subclassing it.
## Instantiating a form
Creating a form is a matter of defining a method to represent that form. This method should return a form object. The
Creating a form is a matter of defining a method to represent that form. This method should return a form object. The
constructor takes the following arguments:
* `$controller`: This must be the controller that contains the form.
@ -59,9 +59,9 @@ The real difference, however, is that you can then define your controller method
## Form Field Types
There are many classes extending `[api:FormField]`. Some examples:
* `[api:TextField]`
* `[api:EmailField]`
* `[api:NumericField]`
@ -73,12 +73,12 @@ There are many classes extending `[api:FormField]`. Some examples:
Full overview at [form-field-types](/reference/form-field-types)
### Using Form Fields
To get these fields automatically rendered into a form element, all you need to do is create a new instance of the
class, and add it to the fieldset of the form.
:::php
$form = new Form(
$controller = $this,
@ -114,7 +114,7 @@ Implementing the more complex fields requires extra arguments.
new TextField(
$name = "FirstName",
$title = "First name"
),
),
new TextField("Surname"),
new EmailField("Email", "Email address")
new DropdownField(
@ -122,7 +122,7 @@ Implementing the more complex fields requires extra arguments.
$title = "Country (if outside nz)",
$source = Geoip::getCountryDropDown(),
$value = Geoip::visitor_country()
)
)
), new FieldSet(
// List the action buttons here
new FormAction("signup", "Sign up")
@ -173,28 +173,28 @@ First of all, you need to create your form on it's own class, that way you can d
$fields = new FieldSet(
new TextField('FirstName', 'First name'),
new EmailField('Email', 'Email address')
);
);
$actions = new FieldSet(
new FormAction('submit', 'Submit')
);
parent::__construct($controller, $name, $fields, $actions);
}
parent::__construct($controller, $name, $fields, $actions);
}
function forTemplate() {
return $this->renderWith(array(
$this->class,
'Form'
));
}
}
function submit($data, $form) {
// do stuff here
}
}
}
`forTemplate()` tells the `[api:Form]` class to render with a template of return value of `$this->class`, which in this case
is *MyForm*, the name of the class. If the template doesn't exist, then it falls back to using Form.ss.
@ -203,36 +203,36 @@ basic customisation:
:::ss
<form $FormAttributes>
<% if Message %>
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
<% else %>
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
<% end_if %>
<fieldset>
<% if Message %>
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
<% else %>
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
<% end_if %>
<fieldset>
<div id="FirstName" class="field text">
<label class="left" for="$FormName_FirstName">First name</label>
$dataFieldByName(FirstName)
</div>
</div>
<div id="Email" class="field email">
<label class="left" for="$FormName_Email">Email</label>
$dataFieldByName(Email)
</div>
</div>
$dataFieldByName(SecurityID)
</fieldset>
<% if Actions %>
<div class="Actions">
</fieldset>
<% if Actions %>
<div class="Actions">
<% control Actions %>$Field<% end_control %>
</div>
<% end_if %>
</div>
<% end_if %>
</form>
`$dataFieldByName(FirstName)` will return the form control contents of `Field()` for the particular field object, in
this case `TextField->Field()` or `EmailField->Field()` which returns an `<input>` element with specific markup
for the type of field. Pass in the name of the field as the first parameter, as done above, to render it into the
for the type of field. Pass in the name of the field as the first parameter, as done above, to render it into the
template.
To find more methods, have a look at the `[api:Form]` class, as there is a lot of different methods of customising the form

View File

@ -67,7 +67,8 @@ Example:
:::php
class MyController extends Controller {
function myurlaction($RAW_urlParams) {
static $allowed_actions = array('myurlaction');
public function myurlaction($RAW_urlParams) {
$SQL_urlParams = Convert::raw2sql($RAW_urlParams); // works recursively on an array
$objs = DataObject::get('Player', "Name = '{$SQL_data[OtherID]}'");
// ...
@ -81,7 +82,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)
*/
function saveAllNames($RAW_data) {
@ -210,7 +210,8 @@ PHP:
:::php
class MyController extends Controller {
function search($request) {
static $allowed_actions = array('search');
public function search($request) {
$htmlTitle = '<p>Your results for:' . Convert::raw2xml($request->getVar('Query')) . '</p>';
return $this->customise(array(
'Query' => DBField::create('Text', $request->getVar('Query')),
@ -239,7 +240,8 @@ PHP:
:::php
class MyController extends Controller {
function search($request) {
static $allowed_actions = array('search');
public function search($request) {
$rssRelativeLink = "/rss?Query=" . urlencode($_REQUEST['query']) . "&sortOrder=asc";
$rssLink = Controller::join_links($this->Link(), $rssRelativeLink);
return $this->customise(array(

View File

@ -29,6 +29,8 @@ the form in a method on *HomePage_Controller*.
:::php
class HomePage_Controller extends Page_Controller {
static $allowed_actions = array('BrowserPollForm');
// ...
function BrowserPollForm() {
@ -415,4 +417,4 @@ In this tutorial we have explored forms, and seen the different approaches to cr
decide to use the [userforms module](http://silverstripe.org/user-forms-module) or create a form in PHP depends on the situation and flexibility
required.
[Next Tutorial >>](4-site-search)
[Next Tutorial >>](4-site-search)