Authorization For Nested Objects

Problem: Authorizing Associations Between Models

I have two ActiveRecord models associated in a many-many relationship using a join table. I am wondering how to reasonably approach authorization using RBAC. My main concern is whether my approach ties my models too closely with the logic used to grant access to them, which would create nasty dependencies for me down the road.

Business Rules

Let’s call my two models Foo and Bar:

  • A user can edit a Foo if and only if they own Foo.

  • A user can edit a Bar if and only if they own Bar.

  • A user can associate a Foo with a Bar if and only if they own both Foo and Bar.

My Current Approach:

Currently, I’ve created model methods to perform the ownership check and wrapped each business rule in its own authorization item:




// Bizrule 1: "Manage Foo"

// Allows user to edit/view a given Foo.

// Controller Route: /foos/{fooId}/view, edit, update

$foo = Foo::model()->findByPk($params["fooId"]);

return $foo && $foo->isOwnedBy($params["userId"]);


// Bizrule 2: "Manage Bar"

// Allows user to edit/view a given Bar.

// Controller Route: /bars/{barId}/view, edit, update

$bar = Bar::model()->findByPk($params["barId"]);

return $bar && $bar->isOwnedBy($params["userId"]);


// Bizrule 3: "Associate Foo Bar"

// Allows user to add or delete an association between a foo and a bar.

// Controller Route: /foos/{fooId}/bars/addBar, removeBar

// Controller Route: /bars/{barId}/foos/addFoo, removeFoo

$foo = Foo::model()->findByPk($params["fooId"]);

$bar = Bar::model()->findByPk($params["barId"]);

return $foo && $bar && $foo->isOwnedBy($params["userId"]) && $bar->isOwnedBy($params["userId"]);



accessRules() configuration

In any cases 1 and 2 there is only one record ID and it is in the query string. In case 3 there are two IDs. Hence my access rules look a bit different:




class FoosController extends CController

{

    ...

    public function accessRules()

    {

        $request = Yii::app()->getRequest();

        return array(

            array(

                // HTTP: GET, POST /foos/{id}

                'allow',

                'actions' =>array('editFoo'),

                'roles' =>array('Manage Foo' =>array(

                    'fooId' =>$request->getQuery('id'),

                )),

            ),

            array(

                // HTTP: POST {barId} to /foos/{id}/bars

                'allow',

                'actions' =>array('addBar'),

                'roles' =>array('Associate Foo Bar' =>array(

                    'fooId' =>$request->getQuery('id'),

                    'barId' =>$request->getPost('barId'),

                )),

            ),

            array(

                // HTTP: DELETE /foos/{id}/bars/{barId}

                'allow',

                'actions' =>array('removeBar'),

                'roles' =>array('Associate Foo Bar' =>array(

                    'fooId' =>$request->getQuery('id'),

                    'barId' =>$request->getQuery('barId'),

                )),

            ),

        );

    }

    ...

}



Questions

  • Is it bad to reference POST in the access rules? (Am I assuming too much about my implementation?)

  • Is this approach sustainable/scalable? (Will it bite me in 6 months?)

  • Should I just stick the check in the action? (I don’t like this approach, as it inconsistently locates authorization logic.)

  • How would you approach this problem?

Have you seen this wiki article yet? I found it a great read. Especially when I were unsure about something.

I have, but I don’t think it addresses this issue. Maybe I’m wrong? It seems more concerned with understanding how RBAC performs an access check, whereas I’m more concerned about managing rules for a larger app.

Also I think it sets a bad example by placing access checks in the actions. It is difficult to create reusable action classes if the action itself is responsible for authorizing the request. Much better to place the authorization check in the access control filter. That’s why we have it! :)

(Also, using a filter puts all your access rules in the same place.)

I would say my principles are:

  • Extremely thin Actions or action classes

  • Filters validate the user and the request

  • The controller instantiates the proper model for the action

  • The model validates the request data and modifies state