Difference between #1 and #2 of Configuring controller access rules to default-deny

unchanged
Title
Configuring controller access rules to default-deny
unchanged
Category
How-tos
unchanged
Tags
accessRules, security
changed
Content
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.
~~~
[php]
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',  // deny all users
                'users'=>array('*'),
            ),
        );
    }
    ...
~~~
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](http://www.yiiframework.com/wiki/121/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:
~~~
[php]
// 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', 'users'=>array('*') );

        $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 `
~~~
[php]
public   public function rules() {
   return
   {
        return array(
       //
            // other rules here
       array('deny',
            array('deny', 'users'=>array('*')) // 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
---------------
1. 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.
2. 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.
3. 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.
4. There really should be a better way of doing this without duplicating
framework code.