Starting with the blog tutorial, Yii developers are familiar with the notion of access rules defined in the controller, where the actions are allowed or denied depending on the user's name or role.
class CommentController extends CController {
public function filters()
{
return array( 'accessControl' ); // perform access control for CRUD operations
}
public function accessRules()
{
return array(
array('allow', // allow authenticated users to access all actions
'users'=>array('@'),
),
array('deny'),
);
}
...
Access rules — when enabled with the accessControl
token in filters()
— are processed in order, from top to bottom, stopping at the first match. It's a common practice to place a deny to all at the end, as a catchall to insure that only intended users have access to this controller's actions.
But if no matches are made, Yii defaults to allow, and for many applications this is insecure and dangerous behavior. A developer not paying attention to his rules could find unauthorized users doing unauthorized things.
Better Security = default-deny ¶
Those who are security minded prefer a default-deny approach, because getting rules wrong (accidentally denying behavior you wish to allow) is much more obvious to show up in testing, where it can be fixed. Accidentally allowing behavior you wish to deny is much harder to detect.
Though there is no real substitute for careful coding, for something this important we prefer a more failsafe approach by overriding the access control code to automatically add a default-deny to the list.
The proper place to do this is in your own project-specific class that extends CController
and forms the base for your own individual model controllers. Having a common MyController
class allows you to insert behavior in common to all controllers, and this technique is described in another wiki article:
Ref: Extending common classes to allow better customization
Our approach is to fetch the current controller's rules()
— which are defined in the real controller class for the particular set of actions — and add a default-deny to the list, then process the filters as the original CController
code would:
// in protected/components/MyController.php
class MyController extends CController {
// filterAccessControl()
//
// This replicates the access control module in the base controller and lets us
// do our own special rules that insure we fail closed.
public function filterAccessControl($filterChain)
{
$rules = $this->accessRules();
// default deny
$rules[] = array('deny');
$filter = new CAccessControlFilter;
$filter->setRules( $rules );
$filter->filter($filterChain);
}
...
Then, each of your controllers should extend from this new MyController
class to get these (and potentially other) services.
This method is only called for controllers that define accessControl
in the filters, and in fact some may not need it: the SiteController
used for login handling can be run by anybody. Since they don't define the accessControl
filter, there is no default-deny.
Handling default-allow ¶
Some Controllers don't require any access rules (say, the SiteController
used for login handling), and those rules don't define 'accessControl' in the filters()
section in the first place. For controllers lithem, this code won't be called at all.
But if a controller really does warrant a default-allow, that's easy enough to achieve directly in the rules() with `
public function rules() {
return array(
// other rules here
array('allow') // default allow
);
}
Even those not implementing this article's technique would do well to add the default-allow rule even though it would be handled by Yii automatically so that others reading the code would know this was intended behavior.
Important Notes ¶
- This code duplicates code from the framework's
CController
class, and this may change from release to release. It's important to check out new releases to insure that this key security-related functionality has not changed. This code is good for (at least) Yii 1.1.5, 1.1.6 and 1.1.7. - It's still a very good idea to include your own default-deny at the end of the access rules in all your controllers, as this helps document the security behavior your controller expects. This overriding base class is intended as an failsafe, not a shorthand to allow shortcuts in the rules.
- Those building their applications from the Blog Tutorial base will find that
protected/components/Controller.php
is already defined as that intermediate helper class; this means you can use it directly without defining a new one. - There really should be a better way of doing this without duplicating framework code.
that so-called default allow sure looks like a deny to me!
Thanks for proofreading your article :-P
Excellent article...
Thanks for this article. Very relevant and important.
I'm not sure if the code in the "default allow" was a typo. If it wasn't a typo, it probably deserves a better explanation.
Instead of:
array('deny') // default allow
Should this be
array('allow') // default allow
You know this is a wiki
You can fix the default allow yourself. Done.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.