From 3e27d27f7a0e29b59c9398dbdf79e47e422217cc 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/reference/execution-pipeline.md | 5 +- docs/en/topics/commandline.md | 1 + docs/en/topics/controller.md | 103 ++++++++++++++++++++++-- docs/en/topics/form-validation.md | 12 +-- docs/en/topics/forms.md | 68 ++++++++-------- docs/en/topics/security.md | 10 ++- docs/en/tutorials/3-forms.md | 4 +- 8 files changed, 153 insertions(+), 53 deletions(-) diff --git a/docs/en/howto/csv-import.md b/docs/en/howto/csv-import.md index b5a96c66a..607234d23 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'); } } ## SSViewer template rendering -See [templates](/topics/templates) for information on the SSViewer template system. \ No newline at end of file +See [templates](/topics/templates) for information on the SSViewer template system. diff --git a/docs/en/topics/commandline.md b/docs/en/topics/commandline.md index ba1e648b1..e658998a1 100644 --- a/docs/en/topics/commandline.md +++ b/docs/en/topics/commandline.md @@ -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) { diff --git a/docs/en/topics/controller.md b/docs/en/topics/controller.md index 52c9e5d70..e0ad90d8f 100644 --- a/docs/en/topics/controller.md +++ b/docs/en/topics/controller.md @@ -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. @@ -37,6 +37,96 @@ Request for `/fastfood/order/24/cheesefries` would result in the following to th [Name] => 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 @@ -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' + ); diff --git a/docs/en/topics/form-validation.md b/docs/en/topics/form-validation.md index a93fc5632..df6416f9a 100644 --- a/docs/en/topics/form-validation.md +++ b/docs/en/topics/form-validation.md @@ -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 diff --git a/docs/en/topics/forms.md b/docs/en/topics/forms.md index 40bdcc577..9b3a0cf39 100644 --- a/docs/en/topics/forms.md +++ b/docs/en/topics/forms.md @@ -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
- <% if Message %> -

$Message

- <% else %> - - <% end_if %> - -
+ <% if Message %> +

$Message

+ <% else %> + + <% end_if %> + +
$dataFieldByName(FirstName) -
- + + - + + $dataFieldByName(SecurityID) -
- - <% if Actions %> -
+
+ + <% if Actions %> +
<% control Actions %>$Field<% end_control %> -
- <% end_if %> + + <% end_if %>
`$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 `` 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 diff --git a/docs/en/topics/security.md b/docs/en/topics/security.md index f162f6e53..fed0cdf0f 100644 --- a/docs/en/topics/security.md +++ b/docs/en/topics/security.md @@ -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 = '

Your results for:' . Convert::raw2xml($request->getVar('Query')) . '

'; 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( diff --git a/docs/en/tutorials/3-forms.md b/docs/en/tutorials/3-forms.md index de8e5b708..9fb425ffe 100644 --- a/docs/en/tutorials/3-forms.md +++ b/docs/en/tutorials/3-forms.md @@ -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) \ No newline at end of file +[Next Tutorial >>](4-site-search)