RBAC with granular item control

I can’t seem to get my head around RBAC and I’m hoping I can solve the problem by formulating it verbally… if anyone could offer guidance it would be much appreciated.

We create, edit, approve and publish content. I have a scenario with the following entities:

User

  • $id, $userid, $password, $name, $status… etc

Company

  • $id, $name, $status… etc

=> MANY_MANY - User (in tbl_company_user, has role = Admin or User)

=> HAS_MANY - Project

Project

  • $id, $company_id, $name, $status… etc

=> MANY_MANY - User (in tbl_project_user, has roles = Manager, Editor, Author)

=> BELONGS_TO - Company

=> HAS_MANY - Post

Post

  • $id, $project_id, $name, $status… etc

=> BELONGS_TO - Project

=> HAS_MANY - User (in tbl_post_user, has roles = Editor(any editor in the project!), Author)

Project Managers may invite (assign) company_user to a project.

I could be an Editor in Project 1 and an Author in project 2

I may amend my own Posts - e.g. tbl_post_user I have role Author

I may approve / reject Posts where post_user I have role Editor

I could use RBAC to look at the UNION of tbl_domain_user, tbl_project_user and tbl_post_user

e.g. CREATE VIEW AuthAssignment as

SELECT ( ‘Project_’+project_id+’_’+role_id as itemname, user_id as userid from tbl_project_user )

UNION

SELECT ( ‘Post_’+post_id+’_’+role_id as itemname, user_id as userid from tbl_post_user )

I would then use checkAccess( "<$model><$id><$action>" , $user->id ) to know if a user can access this particular object / action combo…

[u]Questions:

[/u]- Am I on the right track?

  • How did you do it?

  • Any other suggestions?

Ultimately, I would like a permissions model that :

  • indexAction - shows me all the posts I’m allowed to see ( i.e. filters the model->findAll() for me )

  • seeAction - now I’m in the item, what an I do? Edit , approve, or simply just look

Many thanks for reading this far… ! ;)

Or perhaps I should have AuthItem as a view over the post_user table as well?

From: http://www.yiiframework.com/extension/rbac-manager/


$this->checkAccessByData('anyRole', array('pet'=>'doc', 'number'=>123));

seems to be helpfull…

Take a look at ACL for fine-grained and advanced access control.

Regards

Why not use “Bizuness rules” to check for association of user to project or the like? That’s exactly what they are there for and I think that could simplify your design.

@zeroByte the link is broken - could you please update? Or perhaps attach the file to your response?

@Boaz - this works for individual items at CRUD level, but I struggle to see how it can be used in the index action. I need to restrict access in the list view also.

Hi

I have a scope field in each table. So each record has its own scope.

e.g.

scope = 0 (Fully restricted, user can not CRUD this record; system record.)

scope = 1 (Semi restricted, user can read record and use it in a relation to a FK, but user may not update or delete it.)

scope = 2 (User record, user may CRUD.)

Then I have permissions consisting of:

controller/create/scope

controller/read/scope

controller/update/scope

controller/delete/scope.

The user gets permissions (via roles).

User1:

redController/read/1

redController/read/2

redController/update/2

So if the user goes to actionIndex in the redController, you can determine that the user may only view records with scope 1 and 2. In actionUpdate you will see that the user can only update records with scope 2.

So this is record-level RBAC. But, it might not be granular enough.

done

I am stuck in Chapter 8 of ‘Agile Web Application Development with Yii 1.1 and PHP5’

I executed rbac command successfully on cmd prompt but When I click on ‘Add user to Project’. It says

Error 403

You are not authorized to per-form this action.

Help me out.

my ProjectController.php code is as follow

<?php

class ProjectController extends Controller

{

/**


 * @var string the default layout for the views. Defaults to '//layouts/column2', meaning


 * using two-column layout. See 'protected/views/layouts/column2.php'.


 */


public &#036;layout='//layouts/column2';





/**


 * @return array action filters


 */


public function filters()


{


	return array(


		'accessControl', // perform access control for CRUD operations


		'postOnly + delete', // we only allow deletion via POST request


	);


}





/**


 * Specifies the access control rules.


 * This method is used by the 'accessControl' filter.


 * @return array access control rules


 */


public function accessRules()


{


	return array(


		array('allow',  // allow all users to perform 'index' and 'view' actions


			'actions'=&gt;array('index','view','adduser'),


			'users'=&gt;array('@'),


		),


		array('allow', // allow authenticated user to perform 'create' and 'update' actions


			'actions'=&gt;array('create','update'),


			'users'=&gt;array('@'),


		),


		array('allow', // allow admin user to perform 'admin' and 'delete' actions


			'actions'=&gt;array('admin','delete'),


			'users'=&gt;array('admin'),


		),	


		array('deny',  // deny all users


			'users'=&gt;array('*'),


		),


		


		);


	


}





/**


 * Displays a particular model.


 * @param integer &#036;id the ID of the model to be displayed


 */


 


 


 /*


public function actionView(&#036;id)


{


	&#036;this-&gt;render('view',array(


		'model'=&gt;&#036;this-&gt;loadModel(&#036;id),


	));


}


/**
  • Displays a particular model.

*/

public function actionView($id)

{

$issueDataProvider=new CActiveDataProvider(‘Issue’, array(

‘criteria’=>array(

‘condition’=>‘project_id=:projectId’,

‘params’=>array(’:projectId’=>$this->loadModel($id)),

),

‘pagination’=>array(

‘pageSize’=>1,

),

));

$this->render(‘view’,array(‘model’=>$this->loadModel($id),‘issueDataProvider’=>$issueDataProvider,));

}

/**


 * Creates a new model.


 * If creation is successful, the browser will be redirected to the 'view' page.


 */


public function actionCreate()


{


	&#036;model=new Project;





	// Uncomment the following line if AJAX validation is needed


	// &#036;this-&gt;performAjaxValidation(&#036;model);





	if(isset(&#036;_POST['Project']))


	{


		&#036;model-&gt;attributes=&#036;_POST['Project'];


		if(&#036;model-&gt;save())


			&#036;this-&gt;redirect(array('view','id'=&gt;&#036;model-&gt;id));


	}





	&#036;this-&gt;render('create',array(


		'model'=&gt;&#036;model,


	));


}





/**


 * Updates a particular model.


 * If update is successful, the browser will be redirected to the 'view' page.


 * @param integer &#036;id the ID of the model to be updated


 */


public function actionUpdate(&#036;id)


{


	&#036;model=&#036;this-&gt;loadModel(&#036;id);





	// Uncomment the following line if AJAX validation is needed


	// &#036;this-&gt;performAjaxValidation(&#036;model);





	if(isset(&#036;_POST['Project']))


	{


		&#036;model-&gt;attributes=&#036;_POST['Project'];


		if(&#036;model-&gt;save())


			&#036;this-&gt;redirect(array('view','id'=&gt;&#036;model-&gt;id));


	}





	&#036;this-&gt;render('update',array(


		'model'=&gt;&#036;model,


	));


}





/**


 * Deletes a particular model.


 * If deletion is successful, the browser will be redirected to the 'admin' page.


 * @param integer &#036;id the ID of the model to be deleted


 */


public function actionDelete(&#036;id)


{


	&#036;this-&gt;loadModel(&#036;id)-&gt;delete();





	// if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser


	if(&#33;isset(&#036;_GET['ajax']))


		&#036;this-&gt;redirect(isset(&#036;_POST['returnUrl']) ? &#036;_POST['returnUrl'] : array('admin'));


}





/**


 * Lists all models.


 */


public function actionIndex()


{


	&#036;dataProvider=new CActiveDataProvider('Project');


	&#036;this-&gt;render('index',array(


		'dataProvider'=&gt;&#036;dataProvider,


	));


}





/**


 * Manages all models.


 */


public function actionAdmin()


{


	&#036;model=new Project('search');


	&#036;model-&gt;unsetAttributes();  // clear any default values


	if(isset(&#036;_GET['Project']))


		&#036;model-&gt;attributes=&#036;_GET['Project'];





	&#036;this-&gt;render('admin',array(


		'model'=&gt;&#036;model,


	));


}





/**


 * Returns the data model based on the primary key given in the GET variable.


 * If the data model is not found, an HTTP exception will be raised.


 * @param integer the ID of the model to be loaded


 */


public function loadModel(&#036;id)


{


	&#036;model=Project::model()-&gt;findByPk(&#036;id);


	if(&#036;model===null)


		throw new CHttpException(404,'The requested page does not exist.');


	return &#036;model;


}





/**


 * Performs the AJAX validation.


 * @param CModel the model to be validated


 */


protected function performAjaxValidation(&#036;model)


{


	if(isset(&#036;_POST['ajax']) &amp;&amp; &#036;_POST['ajax']==='project-form')


	{


		echo CActiveForm::validate(&#036;model);


		Yii::app()-&gt;end();


	}


}





public function actionAdduser(&#036;id)

{

{

$form=new ProjectUserForm;

$project = $this->loadModel($id);

if(!Yii::app()->user->checkAccess(‘createUser’, array(‘project’=>$project)))

{

throw new CHttpException(403,‘You are not authorized to per-form this action.’);

}

// collect user input data

if(isset($_POST[‘ProjectUserForm’]))

{

$form->attributes=$_POST[‘ProjectUserForm’];

$form->project = $project;

// validate user input and set a sucessfull flassh message if valid

if($form->validate())

{

Yii::app()->user->setFlash(‘success’,$form->username . " has been added to the project." );

$form=new ProjectUserForm;

}

}

// display the add user form

$users = User::model()->findAll();

$usernames=array();

foreach($users as $user)

{

$usernames[]=$user->username;

}

$form->project = $project;

$this->render(‘adduser’,array(‘model’=>$form, ‘usernames’=>$usernames));

}

}

}