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?