Accessrules, Rbac, And Bizrules

After a lot of searching and a bit of incorrect guessing, I have turned to my peers for help on this one. For a simplified look at my problem, what I have is Drinks and Ingredients. Each Drink can have many Ingredients and each Ingredient can belong to only one Drink. What I would like is for any user with an ‘owner’ role to be able to create, update, delete any Drink or Ingredient (which I have in place). Next, I would like users with ‘contributor’ role to be able to perform the following operations:

  • Drink: create new Drink and edit/delete any Drinks they have created

  • Ingredient: create/edit/delete Ingredients, only for Drinks they created.

Before I can implement any Ingredient control, I need to be able to control Drink. Here is what I have so far.

Snippets from my console command to create the RBAC hierarchy:




...

$auth->createOperation("createDrink","Create a new Drink");

$auth->createOperation("updateDrink","Update a Drink");

$auth->createOperation("deleteDrink","Delete a Drink");

...

$auth->createOperation("createIngredient","Create a new Ingredient");

$auth->createOperation("updateIngredient","Update an Ingredient");

$auth->createOperation("deleteIngredient","Delete an Ingredient");

$auth->createOperation("createOwnDrinkIngredient","Create a new Ingredient for Own Drink");

$auth->createOperation("updateOwnDrinkIngredient","Update an Ingredient for Own Drink");

$auth->createOperation("deleteOwnDrinkIngredient","Delete an Ingredient for On Drink");

...

$bizRule='return Yii::app()->user->id==$params["drink"]->create_user_id;';

$task=$auth->createTask("updateOwnDrink","Update Own Drink",$bizRule);

$task->addChild("updateDrink");

$task=$auth->createTask("deleteOwnDrink","Delete Own Drink",$bizRule);

$task->addChild("deleteDrink");

...

$role=$auth->createRole("contributor", "Contributor");

$role->addChild("createDrink");

$role->addChild("updateOwnDrink");

$role->addChild("deleteOwnDrink");

$role->addChild("createOwnDrinkIngredient");

$role->addChild("updateOwnDrinkIngredient");

$role->addChild("deleteOwnDrinkIngredient");

...

$role=$auth->createRole("owner", "System Owner");

$role->addChild("createDrink");

$role->addChild("updateDrink");

$role->addChild("deleteDrink");

$role->addChild("createIngredient");

$role->addChild("updateIngredient");

$role->addChild("deleteIngredient");



Then inside of the DrinkController I have:




public function accessRules()

{

	return array(

		...

		array('allow',

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

			'roles'=>array('updateDrink','updateOwnDrink'=>array('Drink'=>$model)),

		),

		array('allow',

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

			'roles'=>array('deleteDrink','deleteOwnDrink'=>array('Drink'=>$model)),

		),

		...

	);

}



CAccessControlFilter indicates that parameters can be passed for RBAC bizRules which is what I am trying here. So, I know what I am trying to accomplish can be done, I just don’t know the proper syntax for making it work. The problems must lie in the $bizRule I have defined and the array I am passing to the ‘roles’ for the update/delete actions (since I don’t have $model defined, there is nothing to pass but I don’t know how to get access to what I need). I’m just not sure how to bring the two in line so that they work.

Once I get this one working, I need to determine how to go one step deeper. I need to ensure that a contributor can add/edit/delete ingredients, but only if they are related to a drink that the user created. I don’t have any of that logic started above because I don’t know where to begin. My (incorrect) guess would be something like:




$bizRule='return Yii::app()->user->id==$params["ingredient"]->drink->create_user_id;';



Can someone out there please guide me through this unfortunate mess?

I am really not fond of RBAC. That’s why I gonna present my own solution to this one here. Just to show that it can be done better :D

The entire modeling can be done with this piece of code: (Assumption: Each Ingredient does only belong to one user)





//The BELONGS_TO/HAS_MANY relations can be defined in the Drink and Ingredient model as usual. Both models load

// the RestrictedActiveRecord behavior whereas the User object must load the RequestingActiveRecord behavior.

// config: "enableGeneralPermissions" => true, 'autoPermissions' => '*', 'autoJoinGroups' => 'All'


$owner = new RGroup();

$owner->alias = 'owner';

$owner->save();


$owner->grant('Drink', '*'); //Assuming CRUD actions for "Drink"


$contributor = new RGroup();

$contributor->alias = 'contributor';

$contributor->save();


//Assign anyone to the group "owner" or "contributor", just what you want

$user1->join('owner');

$user2->join('contributor');

//...


//Assumption: Everyone may see others' drinks and ingredients

$all = new RGroup();

$all->alias = 'All';

$all->grant('Drink', 'read');

$all->grant('Ingredient', 'read');




There’s no need for access control in the controllers. Exceptions are thrown automagically if a user attempts to do something he’s not allowed to do.

Just some advertising. ;D

Regards,

Thanks for the suggestion zeroByte. I am still quite new to Yii and while I see what your code is trying to do in general, I’m not sure where it goes, exactly what it does, or exactly how I would extent it so I decided to keep trying to get mine to work ;)

For anyone stumbling across this post that either wants to accomplish the same thing or wants to suggest a better way for me to do it, here’s what I have now that gets me to the end result I wanted.

Snippets from my RbacCommand.php:




$auth->createOperation("createDrink","Create a new Drink");

$auth->createOperation("updateDrink","Update a Drink");

$auth->createOperation("deleteDrink","Delete a Drink");

$auth->createOperation("createIngredient","Create a new Ingredient");

$auth->createOperation("updateIngredient","Update an Ingredient");

$auth->createOperation("deleteIngredient","Delete an Ingredient");

...

$bizRule='return Yii::app()->user->id==Drink::model()->findByPk($_GET["drinkId"])->create_user_id;';

$task=$auth->createTask("createOwnDrinkIngredient","Create/Update/Delete an Ingredient for Own Drink",$bizRule);

$task->addChild("createIngredient");

...

$bizRule='return Yii::app()->user->id==Drink::model()->findByPk($_GET["id"])->create_user_id;';

$task=$auth->createTask("manageOwnDrink","Update/Delete Own Drink",$bizRule);

$task->addChild("updateDrink");

$task->addChild("deleteDrink");

...

$bizRule='return Yii::app()->user->id==Drink::model()->findByPk(Ingredient::model()->findByPk($_GET["id"])->drink_id)->create_user_id;';

$task=$auth->createTask("manageOwnDrinkIngredient","Create/Update/Delete an Ingredient for Own Drink",$bizRule);

$task->addChild("updateIngredient");

$task->addChild("deleteIngredient");

...

$role=$auth->createRole("contributor", "Contributor");

$role->addChild("createDrink");

$role->addChild("createOwnDrinkIngredient");

$role->addChild("manageOwnDrink");

$role->addChild("manageOwnDrinkIngredient");



Some helpful notes… Ingredients can only be created within the context of a drink. That is, you cannot merely navigate to the create action of an Ingredient and specify a drink. You start at a single Drink and then "add ingredient" from there which has drinkId as the drink te Ingredient will be created for (that is where the $_GET["drinkId"] comes from).

The above RbacCommand snippet shows:

  • the operations that are necessary

  • the task to create an ingredient with bizRule that compares the current user id to the create_user_id of the Drink the ingredient is being created for. If the rule passes, the createIngredient operation is assigned.

  • the task to manage the users own drink with bizRule that compares the current user id with the create_user_id of the drink. If the rule passes, Update and Delete drink operations are assigned.

  • the task to manage the ingredients of a users drink with bizRule that compares the current user id with the create_user_id of the drink the ingredient belongs to. If the rule passes, Update and Delete drink operations are assigned.

  • the "contributor" role is created and the operation to create a drink and the tasks to create an ingredient for those drinks as well as update/delete the users drinks and associated ingredients.

The above seems to be working for me and didn’t require any changes to the controller, just to the RbacCommand.php file. The only thing that seems “not right” is calling the GET variable and findByPk function from within the bizRule. I tried putting that sort of logic into the accessRules as parameters but wasn’t able to get it to work–I couldn’t even pass the id into accessRules to reference it. So, the above it what I will be sticking with for the time being.

Hi,

RBAC is powerful and simple enough, IMHO. I would have done the following:

I never use the createOperation etc method. Simplify your work with some ready made extension that provides a GUI to handle the auth items and their relationship. I use RBAM and I recommend it.

b[/b] I would have went for the following RBAC tree:

3261

simple_rbac_tree.png

Note that the auth items "* own *" need to have a bizrule. The bizrule will compare the ownership of the model being handled.

b [/b]Regarding the bizrules, you can use the following bizrule code in ‘edit own drink’, for example:




$drink = $params['drink'];

$drink_creator_id = $drink->createdBy->id;

if ($drink_creator_id == Yii::app()->user->id) {

  return true;

}

return false;



Two important notes:

  1. the code above assumes that you defined relationship between ‘drink’ and ‘user’ models and its noted in the drink class under the name "createdBy’.

  2. the code above assumes that you pass to the bizrule the ‘drink’ object. For example, the access check in the ‘update’ action could looks as follows:





// $drink is the already loaded drink record.

if ((!Yii::app()->user->checkAccess('edit own drink', array('drink' => $drink))) && (!Yii::app()->user->checkAccess('edit drink'))) {

  throw new CHttpException(401);

}



I hope I didn’t forget anything.

The above approach cuts it for me.

Thanks for the suggestions Boaz. I’ve tried RBAC GUIs before, but they all seemed to be either too complicated with too many unnecessary features (for my needs) or to difficult for a typical user to understand (I am using my current site with drinks and ingredients as a playground to build something I can roll out a client). So, I may take a look at an existing GUI for ideas but will likely roll my own.

That being said, I have a question for you, or anyone really. I am using the following to determine whether a user can perform a given action:

RBAC rules in the database with the following in a given controller (compressed below just for the example):




public function filters()

{

	return array( 'accessControl' );

}

public function accessRules()

{

	return array(

		...

		array('allow', 'actions'=>array('create'), 'roles'=>array('createDrink'), ),

		array('allow', 'actions'=>array('update'), 'roles'=>array('updateDrink'), ),

		array('allow', 'actions'=>array('delete'), 'roles'=>array('deleteDrink'), ),

		array('deny', 'users'=>array('*'), ),

	);

}



With that combination, I do not need to modify anything with the actions. When I start a new project, I can simply copy that from controller to controller, changing the word “Drink” with “Ingredient” and I’m set.

Your example, and numerous other examples in the forum, use "checkAccess" within an action to determine a users access. I assume this approach replaces the need for the "accessControl" filter, but what benefit does that give you? It seems to me, using the "accessControl" filter to evaluate the acessRules is easier since all permissions are defined in this one small block. No need to dig through the rest of the controller to find permissions.

What am I missing here?

RBAM extension I recommended on can be used for no more than a GUI to control your site’s RBAC configuration. I think that “Rights” highly acclaimed module is more than that - which is the reason for why I dropped it initially when I surveyed the territory.

I’m not sure access control filter is even working in tandem with Yii’s RBAC system.

Be sure to check that and if such operation is possible, you still need to make sure that your RBAC system is configured and working Ok.

Also, consider that using a simple “checkAccess()” call in the beginning of every action is (arguably) making your code simpler to read and maintain and reduce the risk of bugs down the road. You can easily copy-paste-modify code between the controller actions when those include a ‘checkAccess()’ in their beginning.

Once you clear out the learning the possibilities - its a matter of personal taste…