RBAC, CMenu & Form Actions

As far as I can see RBAC is aimed at protecting the application data (CRUD…). In all the examples I see it is used in the Controller to prevent unauthorised actions.

Is there any example or discussion about how I could re-use the RBAC data to determine what buttons to present on the View. E.g. I don’t want to have an Update hyperlink on an View if the user is not allowed to perform that action.

Methods I’ve thought of so far:

  • use the controller to pass in an array of buttons to the view - I’ll have to cache the user’s full Authentication data in the session to protect the database

(I’ll probably have to do caching to save the processing in CMenu anyway)

  • use the checkAccess method in the View - is this good practice

I would like to link the CMenu->run (e.g. visible/not) to the RBAC data too?

Any guidance would be appreciated!

You can use something like this in the views to determine whether something can be shown or not, I’m not sure if this is the best way but it definitely works for me.


<?php if(Yii::app()->user->checkAccess('admin')): ?>

    <a href="#">Admin link</a>

<?php endif; ?>

I would go like this:


$this->widget('zii.widgets.CMenu',array(

'items'=>array(

    array('label'=>'Login', 'url'=>array('site/login'), 'visible'=>Yii::app()->user->checkAccess('admin')),

));

This should display Login link if user is admin or whatever RBAC returns true for.

Hope this helps

Cheers,

bettor

@[member=‘MCréatif’], @[member=‘bettor’]

Thanks both for your replies.

Both suggested options hammer the checkAccess() function every time I need to render a page, which will have an impact on performance - esp. if the site scales to many concurrent users.

I still think I have to create an in-memory Menu+Action authorisation map for the user session. That way I save the database from multiple hits.

It’s interesting to see how others are implementing this…

For the benefit of others… here is my components/MainManu.php file. As you can see I use setState and getState to store the menu items in the user session.




//class MainMenu extends CWidget {

Yii::import('zii.widgets.CMenu');

class MainMenu extends CMenu {


	/**

	 * TODO: [re]move this 

	 * Pretends to be a CController->getModule for the CController::createUrl 

	 * call in the view

	 */

	public function getModule() {

		return null;

	}


	public function run() {

		$controller=$this->controller;

		$action=$controller->action;

		$items=array();

		foreach( $this->getItems() as $item ) {

			if( isset( $item['visible'] ) && !$item['visible'] )

				continue;

			$item2=array();

			$item2=$item;

			$item2['label']=$item['label'];

			if( is_array( $item[ 'url' ] ) )

				$item2['url']=$controller->createUrl($item['url'][0],array_splice($item['url'],1));

			else

				$item2['url']=$item['url'];

			$pattern=isset($item['pattern'])?$item['pattern']:$item['url'];

			$item2['active']=$this->isActive( $pattern, $controller->uniqueID, $action->id );

			$items[]=$item2;

		}

		$this->render( 'mainMenu', array( 'items'=>$items ) );

	}


	protected function isActive( $pattern, $controllerID, $actionID ) {

		if( !is_array( $pattern ) || !isset( $pattern[0] ) )

			return false;


		$pattern[0] = trim( $pattern[0], '/' );

		if( strpos( $pattern[0],'/' ) !== false )

			$matched = $pattern[0]===$controllerID.'/'.$actionID;

		else

			$matched = $pattern[0]===$controllerID;


		if( $matched && count( $pattern ) > 1 ) {

			foreach( array_splice($pattern, 1 ) as $name => $value ) {

				if( !isset( $_GET[$name] ) || $_GET[$name] != $value )

					return false;

			}

			return true;

		}

		else

			return $matched;

	}


	public function getItems() {

		$_items=array();

		if( !Yii::app()->user->isGuest ) {

			if( Yii::app()->user->getState( 'mainMenuItems', array() ) == array() ) {

				$_items[] = array( 

					'label'=>'Home',

					'url'=>array( '/site/index' ), 

					'active'=>false,

					'visible'=>true 

				);

				$_items[] = array( 

					'label'=>'Posts', 

					'url'=>array( '/post/list' ), 

					'active'=>false,

					'visible'=>Yii::app()->user->checkAccess( 'postList', array(), true ) 

				);

				$_items[] = array( 

					'label'=>'Comments', 

					'url'=>array( '/comment/list' ), 

					'active'=>false,

					'visible'=>Yii::app()->user->checkAccess( 'commentList', array(), true ) 

				);

				//$_items[] = array('label'=>'Contact', 'url'=>array('/site/contact') );

				//$_items[] = array('label'=>'Login', 'url'=>array('/site/login'), 'visible'=>Yii::app()->user->isGuest );

				//$_items[] = array('label'=>'Logout', 'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest );

				$_items[] = array(

					'label'=>'Help', 

					'url'=>array( '/site/help'), 

					'active'=>false, 'visible'=>true 

				);


				// Save in user session

				Yii::app()->user->setState( 'mainMenuItems', $_items, array() );

			}

			return Yii::app()->user->getState( 'mainMenuItems' );

		} else {

			return array();

		}

	}


}



And in the components/views/mainMenu.php I have:




<?php foreach( $items as $item ): ?>

<li><?php 

	if( isset( $item['isAjax'] ) ) {

		echo CHtml::ajaxLink ( $item['label'], CController::createUrl( $item['url'], $item['urlVars'] ), $item['vars'] );

	} else {

		if( isset( $item['visible'] ) )

			if( $item['visible']==true )

				echo CHtml::link( $item['label'], $item['url'], $item['active'] ? array( 'class'=>'active' ) : array() );

			else

				echo "&nbsp;".$item['label']."&nbsp;";

		else

			echo "&nbsp;<i>".$item['label']."</i>&nbsp;";

	}

?></li>

<?php endforeach; ?>



You are store the users in a php config file or a database table ?

nice post about the menu.

How can I get menu items according the RBAC assignment roles?

such as A role with authitem

A

B

C

User U1 has A role, I want get the menu about the A,B,C.

Any help?

Thks